Introducing Spring MVC test framework


Spring MVC comes with a very useful test framework, which allows doing in-depth testing without even starting a web container. If you struggle maintaining any application based on Spring MVC, this post will show you the basics of the Spring MVC test framework.

What am I testing?

We can distinguish 3 levels of testing for Spring MVC controllers:

  • unit tests: we test only the logic of the controller. Dependencies are mocked/stubbed, there's no Spring, nor web container. Spring MVC POJO programming model allows writing such tests easily. These tests are only useful when there's some complex logic inside controllers.
  • out-of-container integration test: we test the controller logic as well as the surrounding plumbing. This plumbing is typically validation or data binding/serialization enforced by the framework. Dependencies are usually mocked/stubbed, but we need a diminished version of the infrastructure (a mock Spring MVC, but no web container).
  • end-to-end testing: we test the whole stack of our application, from the controller to the database, through the service and data access layers. One can write such tests by starting an embedded Jetty in a JUnit test, and using HtmlUnit or REST-assured for the testing itself.

This post focuses on the second level of testing, by using the Spring MVC test framework, available as of Spring 3.2. Here are some scenarios where this test framework comes in handy:

  • testing inbound/outbound serialization (e.g. JSON to Java for incoming requests and Java to JSON for responses)
  • testing validation rules (forms and JSR 303, enforced by Spring MVC)
  • testing content negotiation (e.g. sending the Accept header with a given value and checking we get the appropriate content-type)
  • testing response codes (e.g. 201 for a successful creation)
  • testing the availability of headers in the response (e.g. the Location after a creation)

The Spring MVC test framework makes all of this a breeze, let's see how to use it to test a simple web controller.

The controller to test

The controller we're about to test is the usual "Hello World" example for Spring MVC:

@Controller
public class SimpleController {
 
    @RequestMapping("/hello")
    @ResponseBody
    public String helloWorld() {
        return "Hello World!";
    }
 
}

Here is a quick reminder about the used annotations:

  • @Controller: to mark the class as a controller. Without this annotation, Spring MVC wouldn't pick up the corresponding bean.
  • @RequestMapping: to map the method on a given URL (and HTTP method if necessary).
  • @ResponseBody: to send the returned object directly into the response. We don't want any view rendering here.

Let's move on to the writing of the test.

The testing framework

Here are the steps to write the test.

Setting up the test class

Setting up a Spring MVC test class is pretty much the same as for any test based on the Spring TestContext Framework, except for one new annotation - @WebAppConfiguration - to create a web version of the Spring application context:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class SimpleControllerTest {
 
    ...
 
    @Configuration
    public static class TestConfiguration {
 
        @Bean public SimpleController simpleController() {
            return new SimpleController();
        }
 
    }
}

Note we're configuring the Spring application context directly in the test, by declaring a static @Configuration inner class. The test framework will pick it up automatically.

Bootstrapping the Spring MVC test environment

The MockMvc will mock all the Spring MVC infrastructure. We just need the Spring application context to create it. We do this in the @Before method of our test:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class SimpleControllerTest {
 
    @Autowired private WebApplicationContext ctx;
 
    private MockMvc mockMvc;
 
    @Before public void setUp() {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(ctx).build();
    }
 
    ...
}

Testing the controller

Our test has 2 steps:

  • creating the mock HTTP request (URL, headers, parameters, etc)
  • checking the HTTP response (content type, content, etc)

The test framework a comprehensive API for request creation and response expectation. Let's use first an explicit version of this API:

@Test public void helloWorld() throws Exception {
    mockMvc.perform(
            MockMvcRequestBuilders.get("/hello").accept(MediaType.TEXT_PLAIN))
            .andExpect(MockMvcResultMatchers.status().isOk())
            .andExpect(MockMvcResultMatchers.content().contentType(MediaType.TEXT_PLAIN))
            .andExpect(MockMvcResultMatchers.content().string("Hello World!"));
}

The code is quite simple to understand, but it remains verbose. We can simplify it by adding the following static imports:

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;

If you have an hard time remembering these classes, note they're all starting with MockMvc*.

The full version of the test benefits from the static imports:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class SimpleControllerTest {
 
    @Autowired private WebApplicationContext ctx;
 
    private MockMvc mockMvc;
 
    @Before public void setUp() {
        this.mockMvc = webAppContextSetup(ctx).build();
    }
 
    @Test public void helloWorld() throws Exception {
        mockMvc.perform(get("/hello").accept(MediaType.TEXT_PLAIN))
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.TEXT_PLAIN))
                .andExpect(content().string("Hello World!"));
    }
 
 
    @Configuration
    public static class TestConfiguration {
 
        @Bean public SimpleController simpleController() {
            return new SimpleController();
        }
 
    }
}

The test method is now shorter and easier to read, thanks to the fluent API.

All of this is quite abstract, and I'm sure any developer needs to see the actual request and response. This can be easily done by adding a static import and using the print ResultHandler:

import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
 
(...)
 
@Test public void helloWorld() throws Exception {
    mockMvc.perform(get("/hello").accept(MediaType.TEXT_PLAIN))
            .andDo(print()) // print the request/response in the console
            .andExpect(status().isOk())
            .andExpect(content().contentType(MediaType.TEXT_PLAIN))
            .andExpect(content().string("Hello World!"));
}

By using andDo(print()), we end up with the following output on the console:

MockHttpServletRequest:
         HTTP Method = GET
         Request URI = /hello
          Parameters = {}
             Headers = {Accept=[text/plain]}

             Handler:
                Type = com.zenika.SimpleController

               Async:
   Was async started = false
        Async result = null

  Resolved Exception:
                Type = null

        ModelAndView:
           View name = null
                View = null
               Model = null

            FlashMap:

MockHttpServletResponse:
              Status = 200
       Error message = null
             Headers = {Content-Type=[text/plain], Content-Length=[12]}
        Content type = text/plain
                Body = Hello World!
       Forwarded URL = null
      Redirected URL = null
             Cookies = []

This should make debugging much simpler. Note ResultHandler is a very useful extension point for any action you need to perform on the response.

Conclusion

This introduction covered the basics of the Spring MVC test framework. We tested a simple GET method, but the framework can do much more: sending parameters and a given content in the request, checking more complex content like JSON in the response, etc. And all of this with an efficient and fluent API. The Spring MVC test framework is the perfect tool to test thoroughly and quickly your web controllers.

Stay tuned, a subsequent post will show you how to test a typical JSON-based REST controller.

Source code


Fil des commentaires de ce billet

Ajouter un commentaire

Le code HTML est affiché comme du texte et les adresses web sont automatiquement transformées.