Mocking HTTP, Mockito style

Tommy Situ,

One of the main differences between mocking and stubbing is the ability to verify the behaviour of test doubles. Mocking lets you isolate the class under test, and verify that the expected actions on dependent interfaces were triggered.

Mockito is probably all you need for in-process mocking in Java unit tests. However microservices-based systems require a different test strategy that involves component testing, in which case the dependencies that you need to mock are HTTP endpoints. One tool that tackles this problem is Hoverfly Java. It is powered by a lightweight service virtualization engine which is capable of capturing and simulating HTTP communication. It provides an expressive DSL to create stubbed server responses, and introduced a request verification feature that is inspired from Mockito in the recent release of v0.7.1.

A simple example

Let’s say we need to test the upgrade function of a subscription service. It charges the customer through a payment service, then calls an account service to upgrade the user account type to “premium” on successful payment. Here is how you could test it with Mockito in a monolith application:

@Test
public void shouldUpgradeAccountIfPaymentIsSuccessful() throws Exception {

    when(paymentService.process(new Charge("id", 20000, GBP)))
            .thenReturn(new ChargeStatus(SUCCESSFUL));

    serviceUnderTest.upgrade("id");

    verify(accountService).update(eq("id"), eq(PREMIUM));
}

If the dependencies become RESTful services, writing the same test will require mocking some HTTP endpoints. This can be done without much hassle using Hoverfly Java DSL and Verification API.

@Test
public void shouldUpgradeAccountIfPaymentIsSuccessful() throws Exception {

    hoverflyRule.simulate(dsl(
            service("api.payment.com")
                .post("/v1/charges")
                    .body(equalsToJson(json(new Charge("id", 20000, GBP))))
                    .willReturn(success()),
            service("api.account.com")
                .put("/v1/account/id")
                    .body(equalsToJson("{\"type\":\"premium\"}"))
                    .willReturn(success())
    ));

    serviceUnderTest.upgrade("id");

    hoverflyRule.verifyAll();
}

This test exercises a large part of the component including its HTTP client invoking API endpoints, making real network calls, and handling the requests and responses in a controlled environment. It uses Hoverfly to simulate the desired interactions with multiple services, and invoke verifyAll to check all the expected requests were made as the last step.

What do we want to verify?

Similar to verifying a mocked object, we usually want to verify that a mocked HTTP endpoint

  • has been called;
  • and with the correct request data;
  • and for a certain number of times;
  • and with multiple requests in the correct order;
  • or has never been called.

Verification made easy

Hoverfly Java shares many common verification features which you may have come across in Mockito.

Its verify method takes two arguments:

verify(service("api.payment.com").get("/v1/transactions"), times(2))

The first one is a RequestMatcherBuilder which is also used by the DSL for creating simulations. It lets you define your request pattern, and Hoverfly uses it to search its journal to find the matching requests. The second one is a VerificationCriteria which defines the verification criteria, such as the number of times a request was made. If the criteria are omitted, Hoverfly Java expects the request to have been made exactly once.

Some VerificationCriteria are provided out-of-the-box and are self-explanatory:

times(1)
atLeastOnce()
atLeast(2)
atMost(2)
never()

VerificationCriteria is a functional interface, meaning that you can provide your own criteria with a lambda expression. For example, you can create a more complex assertion on multiple request bodies, such as checking the transaction amount in a Charge object should keep increasing over time:

verify(service("api.payment.com").post("/v1/transactions").anyBody(),

        (request, data) -> {

            // Replace with your own criteria
            data.getJournal().getEntries().stream()
                    .sorted(comparing(JournalEntry::getTimeStarted))
                    .map(entry -> entry.getRequest().getBody())
                    .map(body -> {
                        try {
                            return new ObjectMapper().readValue(body, Charge.class);
                        } catch (IOException e) {
                            throw new RunTimeException();
                        }
                    })
                    .reduce((c1, c2) -> {
                        if(c1.getTransaction() > c2.getTransaction()) {
                            throw new HoverflyVerificationError();
                        }
                        return c2;
                    });
});

Sometimes you want to verify that a service has not been called at all. This use case is equivalent to using the verifyZeroInteractions method in Mockito. An example would be verifying that a notification service is not invoked if a user alert setting is switched off.

verifyZeroRequestTo(service("api.notification"));

In Mockito, you can do “loose matching” of any data you are not interested in. The same can be done in Hoverfly-Java with predefined matchers:

verify(service("api.payment.com").anyMethod(anyPath()).anyQueryParams().anyBody())

If all you are intending to do is verify every stubbed request was made, you can simply use verifyAll().

Conclusion

We have seen an increasing demand for HTTP mocking when dealing with microservices. Just like mocking with Mockito in a unit test, it should be equally simple and quick to mock HTTP resources using Hoverfly Java’s verification feature.

Ordered verification is not covered at the moment, but it is on the roadmap. This feature would be quite useful for testing components that use an async or reactive HTTP client, so that in some cases you can verify requests were made in the right order.

Further reading