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 !!
Leave a Reply