Tag Archives: mockito

Spring MVC integration test with REST Assured and MockMvc

Spring controllers can provide a lot of functionality and in most applications I have seen the web layer code is scattered among java code, annotations and XML config files. Even in the simplest controllers there is a lot that can go wrong – there are request mappings and methods, returned content type, response body, request parameters and calls to services in the layer below. I have always wondered what is the best way to test it all together? Obviously you can get the application up and running in order to execute acceptance tests (headless or via a browser) but this can be a bit of an overkill. Another option is to write a unit test but then arguably you would test the controller out of the context (but you may still want to do this anyway).

Ideally, you would have a test which does not require running a servlet container (memory requirements, start-up time, extra dependencies and good old faff to set it up) and enables testing all functionality (code and meta-code). We want an integration test which will not be limited to a simple Java class (fed with mock HTTP servlet request/response) but will go through the DispatcherServlet and enable us to work with HTTP requests and responses so that we test what is actually produced by our application.

In version 3.2 Spring introduced MockMvc which is a huge improvement and enables us to easily implement this type of tests. When combined with REST Assured (“Java DSL for easy testing of REST services”) we get to use fluent API (including given, when, then) to create code that is expressive as well as easy to read and understand.

Let’s take a look at a ‘hello world’ controller and method:

@RequestMapping(value = "/hello", 
                method = RequestMethod.GET, 
                produces = "application/json;charset=UTF-8")
public @ResponseBody String hello(
            @RequestParam(required = false, 
                defaultValue = "world") String name) {
    return format("Hello {0}!", name);
}

Not much code but emotions are high – we map all GET requests to ‘hello’ url to this method and return JSON content. We can take an optional String parameter called ‘name’, should it not be included in the request we will use “world” value. So how do I test it?

@Test public void 
getHello() {
    given().
    when().
        get(HELLO).
    then().
        statusCode(HttpServletResponse.SC_OK).
        contentType("application/json").
        body(equalTo("Hello world!"));
}  

@Test public void 
getHelloWithParam() {
    given().
        param("name", "coder").
    when().
        get(HELLO).
    then().
        statusCode(HttpServletResponse.SC_OK).
        contentType("application/json").
        body(equalTo("Hello coder!"));
}

Also, we can test that POST method is not supported:

@Test public void
failPostToHello() {
    given().
    when().
        post(HELLO).
    then().
        statusCode(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
}

Now let’s have a look at the test setup:

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(
        "classpath:test-mvc-dispatcher-servlet.xml")
public class HelloControllerTest {
    
    private MockMvc mockMvc;
    
    @Autowired
    private WebApplicationContext context;
    
    @Before
    public void setUp() {
        mockMvc = MockMvcBuilders
                .webAppContextSetup(context).build();
        RestAssuredMockMvc.mockMvc = mockMvc;
    }
    ...	
}

The ContextConfiguration annotation tells Spring about the location of the config file (test-mvc-dispatcher-servlet.xml) which allows for configuration of the WebApplicationContext which, thanks to WebAppConfiguration annotation, gets auto-wired in the context variable which is used to build mockMvc. To quote the javadoc MockMvc is “main entry point for server-side Spring MVC test support”, basically it encapsulates all your web configuration and makes it available to the test. Note that I modified the context configuration so component scan is limited to controllers only (web package) and I can mock the other beans using Mockito.

<context:component-scan 
        base-package="com.czeczotka.spring.web"/>

<mvc:annotation-driven />

<bean id="newsService" 
        class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg 
        value="com.czeczotka.spring.service.NewsService" />
</bean>

Let’s discuss something a little bit more useful. How about testing controller’s interaction with services as well as the response body (which is a slightly more complicated this time)? I created a simple News class (String headline; String article; LocalDateTime timestamp) and a controller to return the latest news. I think this is where REST Assured and MockMvc combined shine – I can set up controllers interactions with the service using Mockito and still test http response. Note the fully qualified import of Mockito’s org.mockito.Mockito.when() method.

@Test public void
getLatestNews() {
    LocalDateTime dateTime = LocalDateTime.now ();
    String now = formatter.format (dateTime);
    org.mockito.Mockito.
        when (newsService.getLatestNews ()).
        thenReturn (new News ("Some news!", 
                    "These are some news!", dateTime));

    given ().
    when ().
        get (LATEST).
    then ().
        statusCode (HttpServletResponse.SC_OK).
        contentType ("application/json").
        body ("headline",  equalTo ("Some news!")).
        body ("article",   equalTo ("These are some news!")).
        body ("timestamp", equalTo (now));
}

@Test public void
getLatestNewsWithSeveralResults () {
    int number = 3;
    LocalDateTime dateTime = LocalDateTime.now ();
    String now = formatter.format (dateTime);
    org.mockito.Mockito.
        when (newsService.getLatestNews (number)).
        thenReturn (Arrays.asList (
            new News ("Some news! 0", 
                "These are some news! 0", dateTime),
            new News ("Some news! 1", "
                These are some news! 1", dateTime),
            new News ("Some news! 2", 
                "These are some news! 2", dateTime)
        ));

    given ().
    when ().
        get (LATEST + "/" + number).
    then ().
        statusCode (HttpServletResponse.SC_OK).
        contentType ("application/json").
        body ("size()", equalTo (number)).
        body ("[0].headline",  equalTo ("Some news! 0")).
        body ("[0].article",   
                    equalTo ("These are some news! 0")).
        body ("[0].timestamp", equalTo (now)).
        body ("[1].headline",  equalTo ("Some news! 1")).
        body ("[1].article",   
                    equalTo ("These are some news! 1")).
        body ("[1].timestamp", equalTo (now)).
        body ("[2].headline",  equalTo ("Some news! 2")).
        body ("[2].article",   
                    equalTo ("These are some news! 2")).
        body ("[2].timestamp", equalTo (now));
}

We can ask for some logging information, in the example we can log all or only some elements of the request/response such as params, body, headers, cookies, method or path. There is also an option to log on failed validation (log().ifValidationFails()):

@Test public void
getHelloWithLogging() {
    given ().
        log().all ().
    when ().
        get (HELLO).
    then ().
        log().all ().
        statusCode (HttpServletResponse.SC_OK);
}

I hope you found it helpful, to me the MockMvc and REST Assured combo definitely changes the way I think about testing Spring controllers and gives me much more confidence in the final solution.

The code

The code of this post is on github so follow the link below to browse it.

http://github.com/czeczotka/spring-mvc-integration-test

You can easily clone and play with it locally by simply following these steps.

$ git clone https://github.com/czeczotka/spring-mvc-integration-test.git
$ cd spring-mvc-integration-test
$ mvn test

Links