1. Introduction
Most non-trivial applications, especially in today’s microservices world, will call other services using a REST API to perform a task.
Unit testing parts of your application that call REST APIs can be difficult and error prone when just using JUnit and Mockito.
If you use Spring’s RestTemplate
to call REST services, you could inject a mocked instance of it into your subject under test and stub the relevant method:
Java
RestTemplate restTemplate = mock(RestTemplate.class);
TodoList list = new TodoList("1", "To do today");
Mockito
.when(restTemplate.getForObject(
"http://localhost:8080/lists/1",
TodoList.class)
).thenReturn(list);
Kotlin
val restTemplate = mock(RestTemplate::class.java)
val list = TodoList("1", "To do today")
Mockito.
`when`(restTemplate.getForObject(
"http://localhost:8080/lists/1",
TodoList::class.java)
).thenReturn(list)
But to know exactly which RestTemplate
method to stub (getForObject
?, getForEntity
?, exchange
?) , you need to dig into the implementation details of your class, which means your test knows more than it really should. We always prefer black box tests as they are more maintainable: if the implementation of the subject under test changes in a way that doesn’t change the behavior, a black box test doesn’t require any changes either.
Since the interface between our application and the external REST API is HTTP, it would actually be a lot easier if we could stub an actual HTTP request/response pair.
2. Enter Wiremock
WireMock is a tool for mocking HTTP responses and although it can run standalone, it also integrates very nicely into unit tests. Wiremock will simply:
- spin up a Jetty server
- respond to HTTP requests that are triggered from your unit test according to stubbed behaviour you define in your unit test
- stop the Jetty server when your unit test has finished
To use it, add the dependency to your pom.xml
or build.gradle
:
Maven
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-jre8</artifactId>
<version>2.26.3</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
</exclusion>
</exclusions>
</dependency>
Gradle
testImplementation 'com.github.tomakehurst:wiremock-jre8:2.26.3' {
exclude group: 'asm', module: 'asm'
}
Two things to note here:
- Use the
wiremock-jre8
artifact unless you need to be on JDK 7 in which you should use thewiremock
one instead: it holds back the Jetty dependency at version9.2.28.v20190418
which is the last version to support JDK 7 - The
asm
exclusion is to avoid a dependency conflict if another dependency in your project (transitively) depends onasm
. Wiremock usesjson-path
which usesasm
for some features, but Wiremock doesn’t seem to use any of those features, so it’s safe to exclude theasm
dependency.
For JUnit 4, a WireMockRule
is available that will automatically start the WireMock server before every test and stop it after, but this has not been ported to a JUnit 5 extension. And it’s really just a few lines of code:
Java
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
class MyServiceTest {
private WireMockServer wireMockServer;
@BeforeEach
public void setup() {
wireMockServer = new WireMockServer(WireMockConfiguration
.wireMockConfig()
.dynamicPort());
wireMockServer.start();
}
@AfterEach
public void tearDown() {
wireMockServer.stop();
}
}
Kotlin
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import com.github.tomakehurst.wiremock.WireMockServer
import com.github.tomakehurst.wiremock.core.WireMockConfiguration
internal class MyServiceTest {
private lateinit var wireMockServer: WireMockServer
@BeforeEach
fun setup() {
wireMockServer = WireMockServer(WireMockConfiguration
.wireMockConfig()
.dynamicPort())
wireMockServer.start()
}
@AfterEach
fun tearDown() {
wireMockServer.stop()
}
}
WireMockConfiguration.wireMockConfig().dynamicPort()
creates a WireMockConfiguration
that indicates the server can listen on any available port. This is convenient as a predefined port hard-coded into your unit test might not always be available, especially on a continuous build server like Jenkins where several tests might be running at the same time. Once the server has started you can get the actual port using wireMockServer.port()
, or the entire base URL of the server using wireMockServer.baseUrl()
.
We recommend using wireMockServer.baseUrl()
as it will include the host name or IP of the actual interface WireMock is listening on: we’ve seen cases where instead of using localhost
WireMock picked a physical network interface to listen on and tests that had hard-coded "localhost"
suddenly failed.
That’s it! We have a web server running in our unit test. Time to add some behavior to it so it can act as a stand-in for the real-life REST API our application calls.
3. Stubbing HTTP responses
Wiremock will let you create stubs for almost anything HTTP can do. The main method here is WireMockServer.stubFor
, which will let you stub based on the HTTP method (GET
, POST
, PUT
, etc.), request headers, request body, etc.; Wiremock calls the parameters to which a stub applies the mapping.
Let’s start with a stub for a simple GET
mapping.
3.1. Stubbing a GET
response
Let’s say you have a UserService
that has a getUsers()
method that does a GET call to a REST service at <baseUrl>/users
, e.g. https://example.com/api/users
, which returns the users as a JSON response:
Java
@lombok.Data
public class User {
private final String username;
@JsonCreator
public User(@JsonProperty("username") String username) {
this.username = username;
}
}
@Service
public class UserService {
private String baseUrl;
private RestTemplate restTemplate;
public UserService(
@Value("${userservice.url}") String baseUrl,
RestTemplate restTemplate
) {
this.baseUrl = baseUrl;
this.restTemplate = restTemplate;
}
public List<User> getUsers() {
ResponseEntity<List<User>> responseEntity = restTemplate.exchange(
baseUrl + "/users",
HttpMethod.GET,
null,
new ParameterizedTypeReference<>() { }
);
return responseEntity.getBody();
}
}
Kotlin
class User @JsonCreator constructor(
@JsonProperty("username")
val username: String
)
@Service
class UserService(
@Value("\${userservice.url}") private val baseUrl: String,
private val restTemplate: RestTemplate
) {
fun getUsers(): List<User> {
val responseEntity = restTemplate.exchange(
"$baseUrl/users",
HttpMethod.GET,
null,
object : ParameterizedTypeReference<List<User>>() { })
return responseEntity.body!!
}
}
To test it with a REST service mocked by WireMock, you’ll stub a GET
response, using WireMock.get
, and then point your UserService
to WireMock instead of to the real REST service:
Java
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
public class UserServiceTest {
private UserService userService;
private WireMockServer wireMockServer;
@BeforeEach
public void setup() {
wireMockServer = new WireMockServer(WireMockConfiguration
.wireMockConfig()
.dynamicPort());
wireMockServer.start();
userService = new UserService(
wireMockServer.baseUrl(),
new RestTemplateBuilder().build());
}
@AfterEach
public void tearDown() {
wireMockServer.stop();
}
@Test
public void testGetUsers() {
// Given
wireMockServer.stubFor(
get(urlEqualTo("/users"))
.willReturn(
aResponse()
.withStatus(200)
.withHeader("Content-Type", "application/json")
.withBody("[\n" +
" { \"username\": \"john\" },\n" +
" { \"username\": \"mary\" }\n" +
"]")
)
);
// When
List<User> users = userService.getUsers();
// Then
assertThat(users, contains(
hasProperty("username", is("john")),
hasProperty("username", is("mary"))
));
}
}
Kotlin
import com.github.tomakehurst.wiremock.client.WireMock.get
import com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo
import com.github.tomakehurst.wiremock.client.WireMock.aResponse
class UserServiceTest {
private lateinit var userService: UserService
private lateinit var wireMockServer: WireMockServer
@BeforeEach
fun setup() {
wireMockServer = WireMockServer(WireMockConfiguration
.wireMockConfig()
.dynamicPort())
wireMockServer.start()
userService = UserService(
wireMockServer.baseUrl(),
RestTemplateBuilder().build())
}
@AfterEach
fun tearDown() {
wireMockServer.stop()
}
@Test
fun testGetUsers() {
// Given
wireMockServer.stubFor(
get(urlEqualTo("/users"))
.willReturn(
aResponse()
.withStatus(200)
.withHeader("Content-Type", "application/json")
.withBody("""
[
{ "username": "john" },
{ "username": "mary" }
]""".trimIndent())
)
)
// When
val users = userService.getUsers()
// Then
assertThat(users, contains(
hasProperty("username", `is`("john")),
hasProperty("username", `is`("mary"))
))
}
}
3.1.1. Matching on query parameters
If your request has query parameters, you can also match on query parameters by including them in the relative URL you pass to urlEqualTo
.
Let’s add a method getUsersByUsername
to our UserService
which uses a query parameter when calling the REST service:
Java
public List<User> getUsersByName(String username) {
ResponseEntity<List<User>> responseEntity = restTemplate.exchange(
baseUrl + "/users?name={name}",
HttpMethod.GET,
null,
new ParameterizedTypeReference<>() { },
username);
return responseEntity.getBody();
}
Kotlin
fun getUsersByName(username: String): List<User> {
val responseEntity = restTemplate.exchange(
"$baseUrl/users?name={name}",
HttpMethod.GET,
null,
object : ParameterizedTypeReference<List<User>>() { },
username)
return responseEntity.body!!
}
We can test this as follows:
Java
@Test
public void testGetUsersByName() {
// Given
wireMockServer.stubFor(
get(urlEqualTo("/users?name=john"))
.willReturn(
aResponse()
.withStatus(200)
.withHeader("Content-Type", "application/json")
.withBody("[\n" +
" { \"username\": \"john\" }\n" +
"]")
)
);
// When
List<User> users = userService.getUsersByName("john");
// Then
assertThat(users, contains(
hasProperty("username", is("john"))
));
}
Kotlin
@Test
fun testGetUsersByName() {
// Given
wireMockServer.stubFor(
get(urlEqualTo("/users?name=john"))
.willReturn(
aResponse()
.withStatus(200)
.withHeader("Content-Type", "application/json")
.withBody("""
[
{ "username": "john" }
]""".trimIndent())
)
)
// When
val users = userService.getUsersByName("john")
// Then
assertThat(users, contains(
hasProperty("username", `is`("john"))
))
}
However, you need to URL encode the query parameters yourself if you do it like this. E.g. if you’re testing your method with a username containing an ampersand (&
), you’d need to stub the REST call like this:
Java
@Test
public void testGetUsersByName() {
// Given
wireMockServer.stubFor(
get(urlEqualTo("/users?name=john%26mary"))
.willReturn(
aResponse()
.withStatus(200)
.withHeader("Content-Type", "application/json")
.withBody("[\n" +
" { \"username\": \"john&mary\" }\n" +
"]")
)
);
// When
List<User> users = userService.getUsersByName("john&mary");
// Then
assertThat(users, contains(
hasProperty("username", is("john&mary"))
));
}
Kotlin
@Test
fun testGetUsersByName() {
// Given
wireMockServer.stubFor(
get(urlEqualTo("/users?name=john%26mary"))
.willReturn(
aResponse()
.withStatus(200)
.withHeader("Content-Type", "application/json")
.withBody("""
[
{ "username": "john&mary" }
]""".trimIndent())
)
)
// When
val users = userService.getUsersByName("john&mary")
// Then
assertThat(users, contains(
hasProperty("username", `is`("john&mary"))
))
}
It’s easier to let WireMock take care of that for you, by using the urlPathEqualTo
method instead of urlEqualTo
and adding a withQueryParam
to it, like this:
Java
@Test
public void testGetUsersByName() {
// Given
wireMockServer.stubFor(
get(urlPathEqualTo("/users"))
.withQueryParam("name", equalTo("john&mary"))
.willReturn(
aResponse()
.withStatus(200)
.withHeader("Content-Type", "application/json")
.withBody("[\n" +
" { \"username\": \"john&mary\" }\n" +
"]")
)
);
// When
List<User> users = userService.getUsersByName("john&mary");
// Then
assertThat(users, contains(
hasProperty("username", is("john&mary"))
));
}
Kotlin
@Test
fun testGetUsersByName() {
// Given
wireMockServer.stubFor(
get(urlPathEqualTo("/users"))
.withQueryParam("name", equalTo("john&mary"))
.willReturn(
aResponse()
.withStatus(200)
.withHeader("Content-Type", "application/json")
.withBody("""
[
{ "username": "john&mary" }
]""".trimIndent())
)
)
// When
val users = userService.getUsersByName("john&mary")
// Then
assertThat(users, contains(
hasProperty("username", `is`("john&mary"))
))
}
3.2. Stubbing a POST
response
Our UserService
should be able to create users, too. So let’s add a method for that:
Java
public String createUser(String username) {
ResponseEntity<Void> responseEntity = restTemplate.exchange(
baseUrl + "/users",
HttpMethod.POST,
new HttpEntity<>(new User(username)),
Void.class);
URI location = responseEntity.getHeaders().getLocation();
int p = location.getPath().lastIndexOf('/');
return location.getPath().substring(p + 1);
}
Kotlin
fun createUser(username: String): String {
val responseEntity = restTemplate.exchange(
"$baseUrl/users",
HttpMethod.POST,
HttpEntity(User(username)),
Void::class.java)
val location = responseEntity.headers.location
val p = location!!.path.lastIndexOf('/')
return location.path.substring(p + 1)
}
Now let’s test it using WireMock!
When stubbing a response that contains a body, you’ll want to include a matcher on the body contents in your stubbing statement. This way you can verify that your code sends the request body you’re expecting. It also means you can create two stubs for the same relative path, which might be necessary depending on what the code you’re testing does.
To create a stub for a POST
call, we use the WireMock.post()
method. A few things are different here from the GET
stub we did in the previous section:
- We’re using
post(urlEqualTo("/users"))
instead ofgeturlEqualTo("/users"))
, as mentioned, to match on aPOST
request - We’re including a
withHeader
to verify that the correctContent-Type
header is being sent, since a request body should always be accompanied by aContent-Type
header describing what type of data is in the body. - We’re not using
equalTo
to match on the request body, since it would do an exact String comparison. Instead, we’ll useequalToJson
which does a more lenient comparison on JSON equivalence. This means that extra spaces or newlines in places where they are not significant will not break the test, nor will a different order of the fields. This way our test is more robust, without compromizing on correctness. - We’re stubbing our
POST
method to return a201 Created
response, which is the normal response for a REST call that creates a resource. We’re also including aLocation
header to the created resource; again, this is a REST best practice. The response has no body, so we’re leaving out thewithBody
part here.
Some REST services will return the resource that was created by a POST
call as the response body of that POST
call. If that is the case, you should include a withBody
in the stub. You can then specify the (JSON) representation that the real REST service would be sending back — and you would include a withHeader
as will to indicate the content type of the response body, e.g. application/json
.
Java
@Test
public void testCreateUser() {
// Given
wireMockServer.stubFor(
post(urlEqualTo("/users"))
.withHeader("Content-Type", equalTo("application/json"))
.withRequestBody(
equalToJson("{ \"username\": \"lucy\" }"))
.willReturn(
aResponse()
.withStatus(201)
.withHeader("Location",
wireMockServer.baseUrl() + "/users/lucy")
)
);
// When
String userId = userService.createUser("lucy");
// Then
assertEquals("lucy", userId);
}
Kotlin
@Test
fun testCreateUser() {
// Given
wireMockServer.stubFor(
post(urlEqualTo("/users"))
.withHeader("Content-Type", equalTo("application/json"))
.withRequestBody(
equalToJson("{ \"username\": \"lucy\" }"))
.willReturn(
aResponse()
.withStatus(201)
.withHeader("Location",
wireMockServer.baseUrl() + "/users/lucy")
)
)
// When
val userId = userService.createUser("lucy")
// Then
assertEquals("lucy", userId)
}
4. Conclusion
WireMock is a great way to test code that does calls to external REST services. We’ve showed some examples of using WireMock to mock a REST service with both GET
and POST
methods.
Using this examples, you can start writing your own WireMock-based unit tests. We haven’t gone into writing negative test cases (for example, returning an error response from a WireMock stub), or using other HTTP methods like PATCH
, DELETE
, or OPTIONS
. But it shouldn’t be too hard to adapt these examples to do just that!
The code for this blog post can be found on my GitHub: https://github.com/fransflippo/wiremock-examples
Happy WireMocking!
Have you used WireMock in your tests?
Let us know your experiences by leaving a comment!