Skip to content

Latest commit

 

History

History
212 lines (160 loc) · 10.2 KB

step-4-write-rest-tests.md

File metadata and controls

212 lines (160 loc) · 10.2 KB

Step 4: Let's write tests for the REST APIs

So far, we focused on being able to run the application locally without having to install or run any dependent services manually. But there is nothing more painful than working on a codebase without a comprehensive test suite.

Let's fix that!!

First Test - Verify our RESTClient

In this section, we'll focus on testing the Pastry API Client component of our application:

Pastry API Client

Let's review the PastryAPIClientTests test class:

@QuarkusTest
public class PastryAPIClientTests {

   @Inject
   @RestClient
   PastryAPIClient client;

   @Test
   public void testGetPastries() {
      // Test our API client and check that arguments and responses are correctly serialized.
      List<Pastry> pastries = client.listPastries("S");
      assertEquals(1, pastries.size());

      pastries = client.listPastries("M");
      assertEquals(2, pastries.size());

      pastries = client.listPastries("L");
      assertEquals(2, pastries.size());
   }

   @Test
   public void testGetPastry() {
      // Test our API client and check that arguments and responses are correctly serialized.
      Pastry pastry = client.getPastry("Millefeuille");
      assertEquals("Millefeuille", pastry.name());
      assertEquals("available", pastry.status());

      pastry = client.getPastry("Eclair Cafe");
      assertEquals("Eclair Cafe", pastry.name());
      assertEquals("available", pastry.status());

      pastry = client.getPastry("Eclair Chocolat");
      assertEquals("Eclair Chocolat", pastry.name());
      assertEquals("unknown", pastry.status());
   }
}

If you run this test class, it should pass and that means we have successfully configured the application to start with all the required containers and that they're correctly wired to the application. Within this test:

  • We're reusing the data that comes from the examples in the Pastry API OpenAPI specification and Postman collection.
  • The PastryAPIClient has been configured as a @RestClient that is wired to the Microcks mock endpoints (see application.properties).
  • We're validating the configuration of this client as well as all the JSON and network serialization details of our configuration!

The sequence diagram below details the test sequence. Microcks is used as a third-party backend to allow going through all the layers:

sequenceDiagram
    PastryAPIClientTests->>+PastryAPIClient: listPastries("S")
    PastryAPIClient->>+RESTClient: get()
    RESTClient->>+Microcks: GET /pastries?size=S
    participant Microcks
    Note right of Microcks: Initialized at test startup
    Microcks-->>-RESTClient: HTTP Response w. JSON[]
    RESTClient-->>-PastryAPIClient: Response
    PastryAPIClient-->>-PastryAPIClientTests: List<Pastry>
Loading

Second Test - Verify the technical conformance of Order Service API

The 2nd thing we want to validate is the conformance of the Order API we'll expose to consumers. In this section and the next one, we'll focus on testing the OrderResource component of our application:

Order Resource Test

We certainly can write an integration test that uses Rest Assured or other libraries to invoke the exposed HTTP layer and validate each and every response with Java assertions like:

when()
   .get("/lotto/{id}", 5)
.then()
   .statusCode(200)
   .body("lotto.lottoId", equalTo(5),
      "lotto.winners.winnerId", hasItems(23, 54));

This certainly works, but presents 2 problems:

  1. It's a lot of code to write! And it needs to be applied to each API interaction, because for each interaction it's probably a good idea to check the structure of same objects in the message. This leads to a fair amount of code!
  2. The code you write here is actually a language specific translation of the OpenAPI specification for the Order API. The the same "rules" get duplicated. Whether you edit the code or the OpenAPI spec first, high are the chances you get some drift between your test suite and the specification you will provide to consumers!

Microcks Dev Services provide another approach by letting you reuse the OpenAPI specification directly in your test suite, without having to write assertions and validation of messages for API interaction.

Let's review the OrderResourceContractTests test class:

@QuarkusTest
public class OrderResourceContractTests extends BaseTest {

   @Test
   void testOpenAPIContract() throws Exception {
      // Ask for an Open API conformance to be launched.
      TestRequest testRequest = new TestRequest.Builder()
            .serviceId("Order Service API:0.1.0")
            .runnerType(TestRunnerType.OPEN_API_SCHEMA.name())
            .testEndpoint("http://host.testcontainers.internal:" + quarkusHttpPort + "/api")
            .build();

      TestResult testResult = MicrocksContainer.testEndpoint(microcksContainerUrl, testRequest);

      // You may inspect complete response object with following:
      //System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(testResult));

      assertTrue(testResult.isSuccess());
      assertEquals(1, testResult.getTestCaseResults().size());
   }
}

Note

For commodity purposes, we've made this test extend BaseTest, giving us access to frequently used environment properties like the quarkusHttpPort.

In this test, we're using a Microcks-provided TestRequest object that allows us to specify to Microcks the scope of the conformance test we want to run:

  • We ask for testing our endpoint against the service interface of Order Service API in version 0.1.0.
  • We ask Microcks to validate the OpenAPI Schema conformance by specifying a runnerType.
  • We ask Microcks to validate the localhost endpoint on the dynamic port provided by the test (we use the host.testcontainers.internal alias for that).

Finally, we're retrieving a TestResult from Microcks containers, and we can assert stuffs on this result, checking it's a success.

The sequence diagram below details the test sequence. Microcks is used as a middleman that actually invokes your API with the example from its dataset:

sequenceDiagram
    OrderControllerContractTests->>+Microcks: testEndpoint()
    participant Microcks
    Note right of Microcks: Initialized at test startup
    loop For each example in Microcks
      Microcks->>+OrderController: HTTP Request
      OrderController->>+OrderService: business logic
      OrderService-->-OrderController: response
      OrderController-->-Microcks: HTTP Response
      Microcks-->Microcks: validate Response
    end  
    Microcks-->-OrderControllerContractTests: TestResult
Loading

Our OrderResource development is technically correct: all the JSON and HTTP serialization layers have been tested!

Third Test - Verify the business conformance of Order Service API

The above section allows us to validate the technical conformance but not the business one!

Imagine we forgot to record all the requested products in the order or change the total price in resulting order. This could raise some issues!

Microcks allows to execute business conformance test by leveraging Postman Collections. If you're familiar with Postman Collection scripts, open the order-service-postman-collection.json file and find some snippets like:

pm.test("Correct products and quantities in order", function () {
    var order = pm.response.json();
    var productQuantities = order.productQuantities;
    pm.expect(productQuantities).to.be.an("array");
    pm.expect(productQuantities.length).to.eql(requestProductQuantities.length);
    for (let i=0; i<requestProductQuantities.length; i++) {
        var productQuantity = productQuantities[i];
        var requestProductQuantity = requestProductQuantities[i];
        pm.expect(productQuantity.productName).to.eql(requestProductQuantity.productName);
    }
});

This snippet typically describes business constraints telling that a valid order response should have unchanged product and quantities.

You can now validate this from your Java Unit Test as well! Let's review the OrderResourcePostmanContractTests test class:

@QuarkusTest
public class OrderResourcePostmanContractTests extends BaseTest {
   @Test
   void testPostmanCollectionContract() throws Exception {
      // Ask for a Postman Collection conformance to be launched.
      TestRequest testRequest = new TestRequest.Builder()
            .serviceId("Order Service API:0.1.0")
            .runnerType(TestRunnerType.POSTMAN.name())
            .testEndpoint("http://host.testcontainers.internal:" + quarkusHttpPort + "/api")
            .build();

      TestResult testResult = MicrocksContainer.testEndpoint(microcksContainerUrl, testRequest);

      // You may inspect complete response object with following:
      //System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(testResult));

      assertTrue(testResult.isSuccess());
      assertEquals(1, testResult.getTestCaseResults().size());
   }
}

Comparing to the code in previous section, the only change here is that we asked Microcks to use a Postman runner for executing our conformance test. What happens under the hood is now that Microcks is re-using the collection snippets to put some constraints on API response and check their conformance.

The test sequence is exactly the same as in the previous section. The difference here lies in the type of response validation: Microcks reuses Postman collection constraints.

You're now sure that beyond the technical conformance, the Order Service also behaves as expected regarding business constraints.

Next