An Introduction to Wiremock

This post provides a brief introduction to Wiremock, showing how it can be used to to quickly and easily mock remote API calls. We'll use Wiremock to write some integration tests for a simple Dropwizard app and show you how it can be put to use in a real world scenario.

Why would I need to mock external API calls? 

There are a number of scenarios where it makes sense to mock an external API rather than call a live service.
  • The external API may still be in development and not yet available for integration. In this instance as long as a data contract has been defined (e.g. Swagger spec, WSDL), the remote API can be stubbed based on the data contract. Stubbed endpoints allows a team to continue development even when external APIs isn't fully implemented.
  • You may have little or no control over the external API uptime in development or test.  As a result you cannot guarantee it will be available to call when running integration tests. In this instance it makes sense to use mocked responses so that your tests don't fail because an external dependency is down. This is especially important if integration tests are being run as part of your Continuous Integration pipeline.  
  • You may want to test fault tolerance scenarios that aren't particularly easy to produce in a live API.  You may want the API to behave badly, but in a very specific way, in order to test how your application deals with a remote failure. Remote calls timing out is an example of a fault tolerance scenario that isn't that easy to set up on a live API. Using a mocked responses allows you to easily test a variety of failure scenarios and ensure your application behaves as expected. 

How does it work? 

Wiremock uses a Jetty Servlet container to expose HTTP endpoints that can be configured to behave in a specific way. Stubbed endpoints are configured to return any HTTP response code, header and body, allowing you to test a wide variety of integration scenarios. Even though stubbed responses are being returned, from the client applications perspective the remote calls appear authentic. As a result the client behaves in exactly the same way it would integrating with a live API.
Wiremock can be deployed as a standalone server, returning mocked responses for preconfigured endpoints. It can also be started and stopped on the fly as part of an integration test suit. This is the approach we're going to take in this post. I like the idea of being able to start the server, configure a stubbed response, run an integration test and then tear down the stub when we're done.  

Sample Code

The sample code is a small Dropwizard app that exposes a single endpoint. An integration test will call the test endpoint, triggering a remote API call which will be serviced by a Wiremock stub.  
  

Customer Resource

The CustomerResource class is a Jersey managed resource that exposes a simple endpoint. In the constructor we pass the external API URL and configure the Client that will be used for the remote call.
On line 22 the Customer that was sent to the endpoint is used to issue a HTTP POST to the credit check API. A CreditCheckResult JSON response is expected from the API, which is then returned to the client.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Path("/customer")
public class CustomerResource {

    private String creditCheckServerUrl;
    private Client client;
 
    public CustomerResource(String creditCheckServerUrl) {
     
        this.creditCheckServerUrl = creditCheckServerUrl; 
        client = ClientBuilder.newClient();
        client.property(ClientProperties.CONNECT_TIMEOUT, 1000);
        client.property(ClientProperties.READ_TIMEOUT,    3000);
    }
    
    @POST
    @Path("/perform-customer-credit-check")
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes({MediaType.APPLICATION_JSON})
    public Response performCustomerCreditCheck(Customer customer) throws Exception {
     
        WebTarget webTarget = client.target(creditCheckServerUrl + "/credit-check-api");      
        Response response = webTarget.request().post(Entity.entity(customer, MediaType.APPLICATION_JSON));
     
        if(response.getStatus() == 200){
            return Response.ok(response.readEntity(CreditCheckResult.class), MediaType.APPLICATION_JSON).build();      
        }
     
        throw new CreditCheckFailedException("Error occurred calling Check Service");
    }
}
Figure 1.0 - customer credit check endpoint

Another component worth mentioning is ApplicationExceptionMapper. This class is used to translate application exceptions into HTTP responses. If a SocketTimeoutException is thrown a 503 Service Unavailable is returned along with a message to say the credit check service call timed out. We'll see this in action later with a fault tolerance integration test. Other exception types result in a 500 Internal Server Error and a generic error message.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
@Provider
public class ApplicationExceptionMapper implements ExceptionMapper<Throwable>
{  
    @Override
    public Response toResponse(Throwable exception)
    {          
     if(exception.getCause() instanceof SocketTimeoutException){
      return Response.status(Response.Status.SERVICE_UNAVAILABLE).
                      entity("Credit Check Service Timed Out").
                      type(MediaType.APPLICATION_JSON).build();
     }
     else{
      return Response.status(Response.Status.INTERNAL_SERVER_ERROR).
                      entity("Error occurred calling Check Service").
                      type(MediaType.APPLICATION_JSON).build();      
     }      
    }
}

                                                                             Figure 1.1 - exception mapper


Integration Test Configuration

To see Wiremock in action we're going to create 3 integration tests, all of which will call the performCustomerCreditCheck endpoint defined above. Before any tests can be written we need to do some general configuration.
  • DropWizardAppRule starts the Dropwizard application before tests are run and stops it again after they finish. This is handy as it saves us having to manually start and stop the server each time we want to run our tests.
  • WireMockRule starts a Jetty container so that Wiremock can serve the mock HTTP responses defined in our tests. Jetty is started on the port specified in the WireMockRule constructor. 
  • Client object is used to call the Jersey endpoint.
  • ObjectMapper is used to serialize/deserialize JSON. 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class CustomerResourceTest {

 @ClassRule    
 public static final DropwizardAppRule<WireMockDemoAppConfig> RULE = 
                 new DropwizardAppRule<WireMockDemoAppConfig>(WireMockDemoApp.class, 
                    ResourceHelpers.resourceFilePath("config.yml"));
 @Rule
 public WireMockRule wireMockRule = new WireMockRule(8090);
 
 private Client client = ClientBuilder.newClient();;     
 private ObjectMapper mapper = new ObjectMapper();


Test 1 - Credit Check Success

Lets start with a happy path test that expects a successful response from the credit check API.  Lines 4 to 10 configure a stub that will expose an endpoint at /credit-check-api . The stub expects a HTTP POST request with an application/json content type and a request body containing Customer JSON. If WireMock receives a request matching this criteria it will return a HTTP 200, an application/json content type and a response body containing the supplied CreditCheckResponse JSON. Note that the getCustomerJson and getCreditCheckResult methods are local helper methods used to build mock JSON for the expected request and response.
 
Now that a stub has been configured for the remote API call, we can call the endpoint we created earlier at /customer/perform-customer-credit-check. On line 13 we do a HTTP POST with a request body containing Customer json.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
    @Test
    public void testCustomerCreditCheckSuccessResponse() throws Exception {

     stubFor(post(urlEqualTo("/credit-check-api"))
                .withHeader("Content-Type", WireMock.equalTo("application/json"))
                .withRequestBody(WireMock.equalTo(getCustomerJson()))
                .willReturn(aResponse()
                    .withStatus(200)
                    .withHeader("Content-Type", "application/json")
                    .withBody(getCreditCheckJson())));
         
     WebTarget webTarget = client.target("http://localhost:8080/customer/perform-customer-credit-check");     Response response = webTarget.request(MediaType.APPLICATION_JSON).
                                  post(Entity.entity(getCustomer(), MediaType.APPLICATION_JSON));
     
     assertThat(response.getStatus(), equalTo(200));     
     assertThat(response.readEntity(CreditCheckResult.class), equalTo(getCreditCheckResult()));
    }

Lets quickly revisit the endpoint we created earlier (figure 1.0 above). The HTTP POST on line 13 above will be handled by the performCustomerCreditCheck endpoint. This endpoint will then make a call out to the credit check API (figure 1.0 line 8), which we configured above as a Wiremock stub. The stub will return a CreditCheckResult response and a HTTP 200, resulting in a similar response being returned by performCustomerCreditCheck.

Test 2 - Credit Check Failure

Next we'll look at a failure scenario that expects an error response from the credit check API. This time the Wiremock stub is configured to return a HTTP 503, testing how our endpoint handles an error response from the remote API.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
    @Test
    public void testCustomerCreditCheckErrorResponse() throws Exception {

     stubFor(post(urlEqualTo("/credit-check-api")).withHeader("Content-Type", WireMock.equalTo("application/json"))
                .withRequestBody(WireMock.equalTo(getCustomerJson()))
                .willReturn(aResponse()
                    .withStatus(503)
                    .withHeader("Content-Type", "application/json")));
                    
     WebTarget webTarget = client.target("http://localhost:8080/customer/perform-customer-credit-check");     Response response = webTarget.request(MediaType.APPLICATION_JSON).
                                  post(Entity.entity(getCustomer(), MediaType.APPLICATION_JSON));
     
     assertThat(response.getStatus(), equalTo(500));     
     assertThat(response.readEntity(String.class), equalTo("Error occurred calling Check Service"));
    }

This test sends a HTTP POST to /customer/perform-customer-credit-checkwhich results in an external call out to the credit check API (figure 1.0 line 8). The stub will return a HTTP 503 which will result in the endpoint throwing a CreditCheckFailedException. The ApplicationExceptionMapper defined earlier (Figure 1.1) will translate the CreditCheckFailedException into a HTTP 500 response which is returned to the client.

Test 3 - Credit Check Service Timeout

Our final test will look at a fault tolerance scenario. The Wiremock stub is configured to return a successful HTTP 200, but this time the response is delayed by 6 seconds. This simulates a slow or unresponsive API call and allows us to test how our application handles such a scenario. It's good practice to terminate external calls if the remote endpoint does not response within a reasonable period.
In the CustomerResource defined earlier (figure 1.0) we configured the Client with a read time out of 3 seconds. This means that if the credit check API doesn't respond within 3 seconds the call will time out and fail. The stub configuration below tests this behaviour by mocking the endpoint to wait 6 seconds before responding, more than enough time for the client application to time out.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Test
    public void testCustomerCreditCheckServiceTimeout() throws Exception {

     int creditCheckServiceDelayMillis = 6000;
     
     stubFor(post(urlEqualTo("/credit-check-api"))                .withHeader("Content-Type", WireMock.equalTo("application/json"))
                .withRequestBody(WireMock.equalTo(getCustomerJson()))
                .willReturn(WireMock.aResponse()
                    .withStatus(200)
                    .withHeader("Content-Type", "application/json")                    
                    .withBody(getCreditCheckJson())
                    .withFixedDelay(creditCheckServiceDelayMillis)));
     
     WebTarget webTarget = client.target("http://localhost:8080/customer/perform-customer-credit-check");     
     long startMillis = DateTime.now().getMillis();
     Response response = webTarget.request(MediaType.APPLICATION_JSON).
              post(Entity.entity(getCustomer(), MediaType.APPLICATION_JSON));
     long endMillis = DateTime.now().getMillis();
     
     assertThat((int)(endMillis - startMillis), is(lessThan(creditCheckServiceDelayMillis)));     
     assertThat(response.getStatus(), equalTo(503));
     assertThat(response.readEntity(String.class), equalTo("Credit Check Service Timed Out"));
    }

This test sends a HTTP POST to /customer/perform-customer-credit-check, which results in an external call out to the credit check API (figure 1.0 line 8). The stub will hang for 6 seconds causing the Client to time out and throw a SocketTimeOutException. The ApplicationExceptionMapper defined earlier (Figure 1.1) will translate the SocketTimeOutException into a HTTP 503 response and include a message saying that the credit check service timed out.

Running the Tests

To see the sample code in action you'll need to pull it down and run it as follows.
  • git clone https://github.com/briansjavablog/wiremock-demo.git
  • cd wiremock-demo
  • mvn test 

What else can Wiremock do?

Wiremock has a number of other interesting features that are worth looking at.
  • Standalone Deployment - Wiremock can be deployed as a standalone web app rather than being started and stopped with each integration test.
  • Mapping files can be used to configure stubs as an alternative to using just the API. This is useful is you have lots of stubs and you want to decouple their configuration from your tests. Mappings can be placed in a directory on your deployed Wiremock instance or registered by posting them to a Wiremock endpoint.
  • Proxying - Wiremock can be used to proxy calls to a live API. Say for example you want to run the majority of your integration tests against a live API, but you'd like to use Wiremock stubs for testing fault tolerance scenarios. Wiremock can be used as a proxy so that it forwards some requests to the live API and responds with stubs for other. This is a powerful feature as it allows you to mix stubbed and real responses in the same suit of tests.   

Wrapping Up 

This post looked at how Wiremock can be used to stub remote endpoints and provide a simple way to test a variety of integration scenarios. From what I've seen Wiremock isn't an alternative to live end to end integration testing, but rather something that can compliment it.  As always, if you have any questions or suggestions, please leave a comment below.

Comments

  1. Hi Brian! Great example... but i'm having trouble with the 'assertThat' methods in CostumerResourceTest.java... I'm getting 'The method assertThat(T, Matcher) in the type Assert is not applicable for the arguments ({diferent type of arguments})'... my 'assertThat' import is 'org.junit.Assert.assertThat'... any ideas? Thank you very much!

    ReplyDelete
  2. Hi Pablo. Have a look at the source for CustomerResourceTest.java at https://github.com/briansjavablog/wiremock-demo/blob/master/src/test/java/com/blog/samples/test/CustomerResourceTest.java. You'll see that the assertThat is used in conjunction with some Hamcrest imports.

    ReplyDelete
  3. I am new to wiremock and as per client requirement , I have to use wiremock in our project but the thing is I have to write wiremock related code in .NET not in Java but I did not find any reference for Wiremock with .NET. Could you please help me with sample code to use wiremock in .NET. requirement : we have a API service , we have to mock that service using wiremock and write Xunit test case in C#.NET

    ReplyDelete

Post a Comment

Popular posts from this blog

Spring Boot & Amazon Web Services (EC2, RDS & S3)

Spring Web Services Tutorial

Health Checks, Metrics & More with Spring Boot Actuator

Spring JMS Tutorial with ActiveMQ

Axis2 Web Service Client Tutorial

Spring Batch Tutorial

Externalising Spring Configuration

Spring Quartz Tutorial