Guide to Mock APIs with WireMock

The microservices architecture allows us to develop, test and deploy different components of an application independently. Though such a component can be developed independently, testing this in isolation can be challenging. For a true integration testing of a microservice, we must test its interaction with other APIs.

WireMock helps in integration testing when we need to mock external APIs for testing a particular API dependent on those external APIs to complete a transaction. WireMock is a popular HTTP mock server that helps in mocking APIs and stubbing responses.

It is worth knowing that WireMock can run as part of an application or a standalone process.

1. Maven Dependency

Start with importing the wiremock dependencies into the project. We can find its latest version in the Maven repo.

<dependency>
   <groupId>com.github.tomakehurst</groupId>
   <artifactId>wiremock-jre8</artifactId>
   <version>2.33.2</version>
   <scope>test</scope>
</dependency>

2. Bootstrapping WireMock

There are a couple of ways for getting started with wiremock. Let’s look at them.

2.1. Using WireMockServer

The simplest way to create WireMockServer instance is to call its constructor. By default, wiremock uses the hostname localhost and port number 8080. We can initialize a WireMockServer with a random/fixed port number and a custom hostname using configureFor() method.

It is very important to start the server before the tests execute, and stop the server after the tests finish. We can reset the mock stubs in between the tests.

The following is an example of setting up wiremock with JUnit 5 tests. Note that this technique can be used in standalone Java applications as well. It is not limited to only tests.

public class WireMockServerTest {

  static WireMockServer wireMockServer = new WireMockServer();

  @BeforeAll
  public static void beforeAll() {

    //WireMock.configureFor("custom-host", 9000, "/api-root-url");
    wireMockServer.start();
  }

  @AfterAll
  public static void afterAll() {

    wireMockServer.stop();
  }

  @AfterEach
  public void afterEach() {

    wireMockServer.resetAll();
  }
}

2.2. Using WireMockRule

The WireMockRule was the preferred way to configure, start and stop the server in JUnit 4 tests, though we can use it in JUnit 5 tests as well. It is very similar to WireMockServer class in features and control.

The following is an example of setting up wiremock with JUnit 4 tests.

public class WireMockServerTest {

  @Rule
  WireMockRule wireMockRule = new WireMockRule();

  @Before
  public void beforeAll() {
    wireMockRule.start();
  }

  @After
  public void afterAll() {
    wireMockRule.stop();
  }

  @AfterEach
  public void afterEach() {
    wireMockRule.resetAll();
  }
}

2.3. Using @WireMockTest

The @WireMockTest annotation is another convenient way to power JUnit tests with wiremock. This is class-level annotation.

@WireMockTest starts the wiremock server before the tests start, stops the server after the end of the tests and cleans the context between tests. So basically, it does all three steps implicitly that we did in previous sections using before and after annotations.

@WireMockTest
public class WireMockTestAnnotationTest {
   //...
}

2.4. Enabling HTTPS

We can enable HTTPS via the httpsEnabled annotation parameter. By default, a random port will be assigned. To fix the HTTPS port number, use httpsPort parameter.

@WireMockTest(httpsEnabled = true, httpsPort = 8443)

With WireMockRule, we can pass the WireMockConfiguration.options() as constructor argument. The same configuration steps work with WireMockServer as well.

WireMockServer wm 
   = new WireMockServer(options().port(8080).httpsPort(8443));

//or

@Rule
public WireMockRule wireMockRule 
   = new WireMockRule(options().port(8080).httpsPort(8443

3. A Simple Example of WireMock

Let’s start with creating a very simple API stub, invoke it using any HTTP client and verify that the mock server was hit.

  • To stub the mock API response, use the WireMock.stubFor() method. It accepts a MappingBuilder instance that we can use to build API mapping information such as URL, request parameters and body, headers, authorization etc.
  • To test the API, we can use any HTTP client such as HttpClient, RestTemplate or TestRestTemplate. We will be using TestRestTemplate in this article.
  • To verify if the request has hit the mock API, we can use WireMock.verify() method.

The following is an example of all three steps with a very simple mock API. This should be able to help in understanding the basic usage of wiremock.

If we prefer to use the BDD language in our tests then we can replace stubFor() with givenThat().

@WireMockTest
public class WireMockTestAnnotationTest {

  @Test
  void simpleStubTesting(WireMockRuntimeInfo wmRuntimeInfo) {
    String responseBody = "Hello World !!";
    String apiUrl = "/api-url";

    //Define stub
    stubFor(get(apiUrl).willReturn(ok(responseBody)));

    //Hit API and check response
    String apiResponse = getContent(wmRuntimeInfo.getHttpBaseUrl() + apiUrl);
    assertEquals(apiResponse, responseBody);

    //Verify API is hit
    verify(getRequestedFor(urlEqualTo(apiUrl)));
  }

  private String getContent(String url) {

    TestRestTemplate testRestTemplate = new TestRestTemplate();
    return testRestTemplate.getForObject(url, String.class);
  }
}

4. Advance Usages

4.1. Configuring API Request

Wiremock gives lots of useful static methods to stub the API request and response parts.

Use get(), put(), post(), delete() and other methods to match corresponding HTTP methods. Use any() to match any HTTP method matching the URL.

stubFor(delete("/url").willReturn(ok()));
stubFor(post("/url").willReturn(ok()));
stubFor(any("/url").willReturn(ok()));

Use other methods such as withHeader(), withCookie(), withQueryParam(), withRequestBody() etc. to set other parts of request. we can pass authorization information as well using the withBasicAuth() info.

stubFor(get(urlPathEqualTo("/api-url"))
        .withHeader("Accept", containing("xml"))
        .withCookie("JSESSIONID", matching(".*"))
        .withQueryParam("param-name", equalTo("param-value"))
        .withBasicAuth("username", "plain-password")
        //.withRequestBody(equalToXml("part-of-request-body"))
        .withRequestBody(matchingXPath("//root-tag"))
        /*.withMultipartRequestBody(
            aMultipart()
                .withName("preview-image")
                .withHeader("Content-Type", containing("image"))
                .withBody(equalToJson("{}"))
        )*/
        .willReturn(aResponse()));

4.2. Configuring API Response

Generally, we are interested in only the response status, response headers and response body. WireMock supports stubbing all these components in the response with easy methods.

stubFor(get(urlEqualTo("/api-url"))
      .willReturn(aResponse()
          .withStatus(200)
          .withStatusMessage("Everything was just fine!")
          .withHeader("Content-Type", "application/json")
          .withBody("{ \"message\": \"Hello world!\" }")));

4.3. Testing API Delay and Timeouts

To test a delayed API response and how the current API handles timeouts, we can use the following methods:

The withFixedDelay() can be used to configure a fixed delay where the response will not be returned until after the specified number of milliseconds.


stubFor(get(urlEqualTo("/api-url"))
   .willReturn(ok().withFixedDelay(2000)));

The withRandomDelay() can be used to get the delay from a random distribution. WireMock supports to types of random distributions: uniform distribution and lognormal distribution.

stubFor(get(urlEqualTo("/api-url"))
  .willReturn(
      aResponse()
          .withStatus(200)
          .withFixedDelay(2000)
          //.withLogNormalRandomDelay(90, 0.1)
          //.withRandomDelay(new UniformDistribution(15, 25))
  ));

We can also use withChunkedDribbleDelay() to simulate a slow network using where the response is received in chunks with time delays in between. It takes two parameters: numberOfChunks and totalDuration.

stubFor(get("/api-url").willReturn(
  aResponse()
    .withStatus(200)
    .withBody("api-response")
    .withChunkedDribbleDelay(5, 1000)));

4.4. Testing Bad Responses

In a microservices architecture, an API can behave abnormally at any time so the API consumers must be ready to handle those cases. Wiremock helps in this kind of response handling by stubbing faulty responses using withFault() method.

stubFor(get(urlEqualTo("/api-url"))
  .willReturn(aResponse()
      .withFault(Fault.MALFORMED_RESPONSE_CHUNK)));

It supports following enum constants:

  • EMPTY_RESPONSE: Return a completely empty response.
  • RANDOM_DATA_THEN_CLOSE: Send garbage then close the connection.
  • MALFORMED_RESPONSE_CHUNK: Send an OK status header, then garbage, then close the connection.
  • CONNECTION_RESET_BY_PEER: Close the connection causing a “Connection reset by peer” error.

5. Verifying API Hits

If we wish to verify that mocked APIs were hit and how many times, we can do so WireMock.verify() method in the following manner.

verify(exactly(1), postRequestedFor(urlEqualTo(api_url))
        .withHeader("Content-Type", "application/JSON"));

There are quite a few methods to verify the hit counts, such as lessThan(), lessThanOrExactly(), exactly(), moreThanOrExactly() and moreThan().

verify(lessThan(5), anyRequestedFor(anyUrl()));
verify(lessThanOrExactly(5), anyRequestedFor(anyUrl()));
verify(exactly(5), anyRequestedFor(anyUrl()));
verify(moreThanOrExactly(5), anyRequestedFor(anyUrl()));
verify(moreThan(5), anyRequestedFor(anyUrl()));

6. Conclusion

This WireMock tutorial will help you in getting started with integration testing by mocking the external REST APIs. It covers the various methods to initialize the WireMockServer and start, stop or reset when needed.

We learned the basic and advanced options to configure the request and response stubbing, match API responses and verify the API hits. We also learned to simulate various success, failure and error cases in mocked APIs.

Happy Learning !!

Sourceocde on Github

Comments

Subscribe
Notify of
guest
1 Comment
Most Voted
Newest Oldest
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.