REST Client Testing With MockRestServiceServer

Functionally testing a REST Client is simple with the new MockRestServiceServer if you are using Spring’s RestTemplate to power the client. This is a new feature in Spring 3.2.x but was available via the spring-test-mvc project starting with Spring 3.1.x (extra spring-test-mvc.jar required). The documentation is a little light in the spring reference manual so hopefully this example can help you piece it together.

Previously you might have had unit tests that mocked the RestTemplate but didn’t fully test the calls and error handling provided with the framework. Or you created an elaborate fake server environment just to spit back valid and invalid responses. MockRestServiceServer takes the approach of mocking the server and allowing you to specify expected behavior and responses in your junit test class. This allows you to fully test your handling of the RestTemplate client and server exception classes.

This example only shows how the mock server works. In a real environment you’d probably use RestTemplate with Jackson for object to json mapping and possibly Spring @Async for asynchronous calls.

SimpleRestService is a sample REST client that makes a call to a URL and handles successes and errors by returning them in the result string. We’ll use this as an example for our junit test cases:

@Service
public class SimpleRestService {
    @Autowired
    private RestTemplate restTemplate;

    public String getMessage() {
        String result;
        try {
            String httpResult = restTemplate.getForObject("http://google.com",
                                      String.class);
            result = "Message SUCCESS result: " + httpResult;
        } catch (HttpStatusCodeException e) {
            result = "Get FAILED with HttpStatusCode: " + e.getStatusCode()
                       + "|" + e.getStatusText();
        } catch (RuntimeException e) {
            result = "Get FAILEDn" + ExceptionUtils.getFullStackTrace(e);
        }
        return result;
    }
}

The only real setup you need for testing is to configure your IDE to find the static imports. In Eclipse this is in Java>Editor>Content Assist>Favorites. Add these to go along with the hamcrest CoreMatchers and junit Assert that you probably already have.
If using Spring 3.2.x:
org.springframework.test.web.client.match.MockRestRequestMatchers
org.springframework.test.web.client.response.MockRestResponseCreators
If using Spring 3.1.x, the static import classes are named differently:
org.springframework.test.web.client.match.RequestMatchers
org.springframework.test.web.client.response.ResponseCreators

Each test will chain expect() and respond() methods. MockRestRequestMatchers offers many hamcrest matchers to check your request URL, headers, HTTP method, and even json and xpath matchers to check body content. MockRestResponseCreators allows you to easily build both success and error responses.

Also, each test must call mockServer.verify() after the RestTemplate call is made to run the Mock Server assertions.

Setup the MockRestServiceServer in the setUp method:

    @Before
    public void setUp() {
        mockServer = MockRestServiceServer.createServer(restTemplate);
    }

testGetMessage() verifies our URL, GET HttpMethod, and returns a 200 Success with a text message of resultSuccess:

    @Test
    public void testGetMessage() {
        mockServer.expect(requestTo("http://google.com"))
                .andExpect(method(HttpMethod.GET))
                .andRespond(withSuccess("resultSuccess", MediaType.TEXT_PLAIN));

        String result = simpleRestService.getMessage();

        mockServer.verify();
        assertThat(result, allOf(containsString("SUCCESS"),
                       containsString("resultSuccess")));
    }

testGetMessage_404() shows a response with the specific 404 Not Found client http status code:

    @Test
    public void testGetMessage_404() {
        mockServer.expect(requestTo("http://google.com"))
                .andExpect(method(HttpMethod.GET))
                .andRespond(withStatus(HttpStatus.NOT_FOUND));

        String result = simpleRestService.getMessage();

        mockServer.verify();
        assertThat(result, allOf(containsString("FAILED"),
                       containsString("404")));
    }

testGetMessage_500() shows usage of the withServerError() convenience method:

    @Test
    public void testGetMessage_500() {
        mockServer.expect(requestTo("http://google.com"))
                .andExpect(method(HttpMethod.GET))
                .andRespond(withServerError());

        String result = simpleRestService.getMessage();

        mockServer.verify();
        assertThat(result, allOf(containsString("FAILED"),
                       containsString("500")));
    }

Additional matcher test examples can be found in the spring-test-mvc section of the spring 3.2.x github repo.

Hopefully the new mock server in Spring helps you as much as it helped me, by cleaning up and reducing the amount of testing code required in both a reusable and standard fashion. The full java code from these examples are on my github page.

One thought on “REST Client Testing With MockRestServiceServer

  1. Hanan says:

    Can you please attach what you in
    /testconfig/applicationContext-test.xml
    configuration file.

  2. Jeff Sheets says:

    No problem. I just added the applicationContext-test.xml to the github repo. I also modified the test class to use the non-transactional AbstractJUnit4SpringContextTests so it is easier to setup.

    Take a look. Hope it helps!

  3. Bernhard says:

    Hi,

    Thanks for your helpful example! One comment:

    I noticed that you are injecting the RestTemplate in your test. There are several issues with storing RestTemplates as instance variables (e.g. the template uses the BasicClientConnectionManager per default (for which the docs say “Even though this class is thread-safe it ought to be used by one execution thread only.”) which can lead to IllegalStateException due to connection reallocation attempts by different threads).

    Hence you probably only want to inject your HttpRequestFactory and create the RestTemplate on the fly when needed using new().

    Just my 2 cents 😉

    1. Jeff Sheets says:

      Bernhard, thanks for the feedback!

      You raise an interesting question with valid points, but I’m not sure that autowiring is an issue with RestTemplate.

      It seems that even Spring’s official blog posts on RestTemplate are autowiring it in and labeling it as being Thread-safe:
      http://blog.springsource.org/2009/03/27/rest-in-spring-3-resttemplate/

      You may want to file a bug with the Spring team to clarify this, since they seem to list RestTemplate as being Thread-safe. And unfortunately I couldn’t find any forum postings related to RestTemplate and autowiring.

      Thanks for the discussion!
      — Jeff

  4. Hanan says:

    Can you please help me with an example of a mocking a request with header parameters.

  5. Jeff Sheets says:

    Hanan, try adding something like this to the mockServer expect line:

    .andExpect(header(“Content-Type”, “application/json”))

    1. Hanan says:

      Thank you for your help; I really appreciate your article
      Another help please
      I don’t know how to add query parameters to get request
      and how could i do post request.

  6. Jeff Sheets says:

    Hanan,
    If you have a REST Client that does a post, then to test that you would change the mockServer to expect a HttpMethod.POST. To check query parameters, one way is to use a different hamcrest matcher on the requestTo() like requestTo(containsString(“param1=myval”). For a POST you might need to check the body with content(), jsonPath(), or xpath()

    There are many usage samples of request matchers here:
    https://github.com/SpringSource/spring-framework/tree/master/spring-test-mvc/src/test/java/org/springframework/test/web/client/samples/matchers

  7. Hanan says:

    But requestTo(containsString(“param1=myval”)
    compare the matcher with request.getURI().toString(), I want to include this parameters in the request body not in the url.

    1. Jeff Sheets says:

      Hi Hanan,
      Please see my full reply via email, but essentially yes you’ll need to check the body with something like this:
      .andExpect(jsonPath(“$.person[0].name”).value(“Hanan”))

  8. Vijay says:

    Hi

    I want to write the test to check rest readTimeOut, Can you please help me to write the test please

  9. Jeff Sheets says:

    Vijay,

    I think you would need to implement a custom ResponseCreator for this purpose that has a sleep() function in it for a specified amount of time, though I haven’t tried it.

  10. Vijay says:

    Thanks Jeff,

    Still am not able to test the readTimeout even after implementing custom ResponseCreator. I am put thread sleep(timeinmilisecond) in createResponse method where timeinmilisecond > readtime I configured in context file and still not getting expected result. But thanks for replay.

  11. Jeff Sheets says:

    Vijay,

    Unfortunately the way that MockRestServiceServer works is that the createServer() method changes the RestTemplate to use a RequestMatcherClientHttpRequestFactory instead of any configured requestFactory that you may have used. So even if you set a readTimeout or connectTimeout on SimpleClientHttpRequestFactory, the RestTemplate won’t use it since it has been changed to use a different requestFactory.

    You’ll need to just throw a SocketTimeoutException from the response to test how your code handles the exception (since I think this is what the Java Connection object will throw on a timeout).

    Try implementing a new TimeoutResponseCreator and finish your mockServer call with .andRespond(withTimeout()):

    public class TimeoutResponseCreator implements ResponseCreator {

    @Override
    public ClientHttpResponse createResponse(ClientHttpRequest request) throws IOException {
    if (true) {
    throw new SocketTimeoutException(“Testing timeout exception”);
    }
    return null;
    }

    public static TimeoutResponseCreator withTimeout() {
    return new TimeoutResponseCreator();
    }
    }

  12. Marco says:

    Hi Jeff,

    thanks for the examples. Can you update the link to the additional matchers? I was in need for the jsonPath matcher and I found this page useful, but the link (second last paragraph before our responses) now is https://github.com/spring-projects/spring-framework/tree/master/spring-test/src/test/java/org/springframework/test/web/client/samples/matchers.

    Thanks,

    Marco

  13. Shailesh says:

    Any chance of creating similar testing for AsyncRestTemplate?

  14. Bob F. says:

    When I try to execute the example I get this error. java.lang.AssertionError: Further request(s) expected
    0 out of 1 were executed

    What am I missing?

  15. Jeff Sheets says:

    Bob F., I’m not sure. Sounds like something didn’t work in the setup. You should be able to clone the repo, run mvn eclipse:eclipse, import it and run the tests.
    https://github.com/jeffsheets/MockRestServiceServerExample

    1. Gabby says:

      I tried running it with no luck,I am getting the same error as Bob F. please help

  16. matt says:

    If you don’t mind, how would I specify a service method in SimpleRestService that was a POST? Is GET the default? I’ve tried using @RequestMapping(method = RequestMethod.POST) but it didn’t work. Thanks for your efforts!

  17. Jeff Sheets says:

    matt, try changing the method to use postForObject instead of getForObject on this line:
    restTemplate.getForObject(

  18. Goran Petrov says:

    Hi, I am wondering if it is possible to execute some kind of a callback when the request from the rest template is received?

  19. Jeff Sheets says:

    Hi Goran,

    Take a look at the new AsyncRestTemplate. It allows you to register a callback.

  20. Sam JYot says:

    HI , I am having the confusion in the scenario where , i am having a dynamic string created inside the service method , and i need to test that the service method returns a string of Any value . In short how do we test only the return type in andRespond(??); .
    Thanks .

  21. Ran says:

    Great article but unfortunately my code below isn’t working. The verify() call fails as the request doesn’t go to my mock server.

    @Autowired
    private SlackWebhooks service;

    private MockRestServiceServer server;

    @Before
    public void setUp() {
    RestTemplate restTemplate = new RestTemplate();
    server = MockRestServiceServer.createServer(restTemplate);
    }

    @Test
    public void invokeIncomingWebhook_When_Invoked_Should_PostCorrectRichMessage() {
    this.server.expect(requestTo(“https://hooks.slack.com/services/asdasd”)).
    andExpect(method(HttpMethod.POST)).
    andRespond(withServerError());

    ResponseEntity response = this.service.invokeSlackWebhook();

    server.verify();

    assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
    }

    Can you please help me out here?

    1. Jeff Sheets says:

      @Ran make sure to inject/autowire the RestTemplate to the test so that it uses the same one that the SlackWebhooks service is using. I think the issue is that this test is using a different RestTemplate so the mock service doesn’t get setup correctly.

      Here’s a test class for comparison: https://github.com/jeffsheets/MockRestServiceServerExample/blob/master/src/test/java/com/jeffsheets/rest/SimpleRestServiceFunctionalTest.java

  22. Lubomír Zrnečko says:

    Hi, Jeff, just want to thank you for your article, it was helpful.
    Also let me say that I really admire your infinite patience with responding to all of the requests for help in comments.

    1. Jeff Sheets says:

      Glad you found it useful, thanks for the nice comments!

Leave a Reply

Your email address will not be published. Required fields are marked *

*

*