Unit testing HTTP calls with LocalTestServer
There are times when you’re unit testing code that is making HTTP calls to a remote server. You could be using a library such as Apache’sHttpClient or Spring’s RestTemplate to do so.
Of course, you don’t want to rely on a remote service for your unit tests. Besides the overhead involved (remember that unit test are supposed to be fast) you simply cannot rely on remote services being available during execution of your tests. You probably are also not able to completely control the response for all of your test scenarios.
Consider the following simplified example.
ExampleHttpCall
public class ExampleHttpCall { private String serviceUrl; public ExampleHttpCall(String url) { serviceUrl = url; } public String doGet() { RestTemplate restTemplate = new RestTemplate(); ResponseEntity<String> responseEntity = restTemplate.getForEntity(serviceUrl, String.class); String response = responseEntity.getBody(); return response; } }
How would you go about writing a unit test for the ExampleHttpCall?
You could of course redesign the class in such a manner that an instance of the RestTemplate gets injected into the class:
ExampleHttpCall alternate version
@Component public class ExampleHttpCallAlternateVersion { @Resource private RestTemplate restTemplate; private String serviceUrl; public ExampleHttpCallAlternateVersion(String url) { serviceUrl = url; } public String doGet() { ResponseEntity<String> responseEntity = restTemplate.getForEntity(serviceUrl, String.class); String response = responseEntity.getBody(); return response; } }
The dependency can now be mocked giving you great control. However, this approach also incurs increased complexity due to additional configuration. Furthermore, you could end up with a lot of tedious mocking.
For this simple example using a mock probably is the way to go. But this may not always be the case. If so, another possible approach employs the use of a local test server. As it happens, the Apache HttpClient project provides a LocalTestServer in its tests artifact. If you’re using Maven you can include it by adding the following dependency:
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.3.6</version> <classifier>tests</classifier> <scope>test</scope> </dependency>
Now you can set up the server in your unit test:
LocalTestServer set up
private LocalTestServer server = new LocalTestServer(null, null); @Before public void setUp() throws Exception { server.start(); } @After public void tearDown() throws Exception { server.stop(); }
Only starting and stopping a server doesn’t get you very far, of course. So there is one more ingredient that you’ll be needing. You’ll be wanting to register one or more handlers that implement the interface org.apache.http.protocol.HttpRequestHandler
, e.g.:
register your handler
server.register("/foo/*", myHttpRequestHandler);
The HttpRequestHanlder interface will have you implement the method void handle(HttpRequest request, HttpResponse response, HttpContext context) throws HttpException, IOException;
This will method will give you full control over the HTTP response.
So for our original example a minimal unit test could look something like the following code:
Basic unit test
public class ExampleHttpCallTest { private ExampleHttpCall exampleHttpCall; private LocalTestServer server = new LocalTestServer(null, null); private HttpRequestHandler myHttpRequestHandler = new HttpRequestHandler() { @Override public void handle(HttpRequest request, HttpResponse response, HttpContext context) throws HttpException, IOException { response.setEntity(new StringEntity("foobar")); } }; @Before public void setUp() throws Exception { server.start(); server.register("/foo/*", myHttpRequestHandler); String serverUrl = "http:/" + server.getServiceAddress(); exampleHttpCall = new ExampleHttpCall(serverUrl +"/foo/bar"); } @After public void tearDown() throws Exception { server.stop(); } @Test public void test() { String result = exampleHttpCall.doGet(); assertEquals("foobar", result); } }
That’s all that it takes to get started. From here on you can elaborate by adding test cases for every possible scenario.
Reference: | Unit testing HTTP calls with LocalTestServer from our JCG partner Wim van Haaren at the JDev blog. |
Just sharing what I tried at https://github.com/achmadns/project-seed/blob/local-test-server/src/test/java/LocalTest.java
Thank you Wim to let us know this useful feature.