How to test… HTTP/REST Service Clients

In this post I will show how to test HTTP/REST service clients without the need for a real service.

TestHttpClients

Testing clients for HTTP/REST services can be quite difficult. To test edge cases you normally need to be able to manipulate the service to return right the data your test requires. Also you don't want to rely on a central service that may or may not be installed in a network that may or may not be reachable.

Let's consider this simple HTTP service 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
31
32
33
34
35
36
37
package de.assertagile.demonstration.howtotest.http;
 
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.GetMethod;
 
import java.io.IOException;
 
public class UserServiceClient {
 
    private HttpClient httpClient = new HttpClient();
    private String serviceUrl;
 
    public UserServiceClient(String serviceUrl) {
        this.serviceUrl = serviceUrl;
    }
 
    public String getUserNameById(String id) throws ServiceUnavailableException {
        GetMethod get = new GetMethod(serviceUrl + id);
 
        try {
            int status = httpClient.executeMethod(get);
            if (status == 200) {
                return get.getResponseBodyAsString();
            } else {
                throw new ServiceUnavailableException("Getting user with ID " + id + " failed with status " + status + "!");
            }
        } catch (IOException e) {
            throw new ServiceUnavailableException("Service not available at " + serviceUrl + "!");
        }
    }
 
    private class ServiceUnavailableException extends Throwable {
        public ServiceUnavailableException(String message) {
            super(message);
        }
    }
}
UserServiceClient.javaview rawview file on GitHub

Solution A: Mock HTTP

Mocking the underlying HTTP client (Apache in this case) can be a solution. However, since the actual result body is just put into the method object and this object typically is initialised within the class under test, this is rather tricky and almost always will result in at least some uncovered lines and to many lines of complicated mocking code.

Solution B: Use WireMock

WireMock is a software which simply creates an in-memory HTTP service. The service behaviour can be programmed right within the test.

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
31
32
33
34
35
36
37
38
39
40
41
42
43
package de.assertagile.demonstration.howtotest.http
 
import com.github.tomakehurst.wiremock.WireMockServer
import com.github.tomakehurst.wiremock.client.WireMock
import spock.lang.Specification
 
import static com.github.tomakehurst.wiremock.client.WireMock.*
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig
 
class UserServiceClientSpec extends Specification {
 
    WireMockServer wireMockServer = new WireMockServer(wireMockConfig().port(8888))
    WireMock wireMock = new WireMock("localhost", wireMockServer.port)
 
    UserServiceClient userServiceClient = new UserServiceClient("http://localhost:${wireMockServer.port}/user/")
 
    def setup() {
        wireMockServer.start()
    }
 
    def cleanup() {
        wireMockServer.stop()
    }
 
    def "Requesting a user name by ID"() {
        given:
        String userId = UUID.randomUUID().toString()
        String expectedUserName = "bob"
 
        and:
        wireMock.register(get(urlEqualTo("/user/${userId}"))
                .willReturn(aResponse()
                .withStatus(200)
                .withBody(expectedUserName)
        ))
 
        when:
        String userName = userServiceClient.getUserNameById(userId)
 
        then:
        userName == expectedUserName
    }
}
UserServiceClientSpec.groovyview rawview file on GitHub

Usage in Black Box Tests

If your application is using an external service, you normally get some instance for testing. But these instances often are shared with other users of the service. The might be unreachable from some networks. The most probably are not will suited for performance and stress tests.

Since WireMock can also be stared as a standalone application somewhere in your network it can help you here as well. You can connect to it using the WireMock class and to change behaviour as needed (just like in the class test example above) or configure some default behaviour (including delay) for performance test. Checkout this documentation for details.

More solutions

There are some more solutions similar to WireMock which I have not tried yet. If you know some nice looking alternative, pleas let me know.

Conclusion

Since mocking HTTP classes is quite hard and using WireMock is rather easy and does work on any system, including any CI system with very little effort, I would always prefer doing the latter in class tests.

The use of WireMock for performance and black box system tests with external services makes this little helper even more valuable.