A Comprehensive Example of a Spring MVC Application - Part 4

Testing the Web Layer

As can be understood from the large section about implementing the Web Layer, it is obvious that this layer requires rigorous tests. In this layer we want to test the REST API only - not the service. So we'll mock the service.

 

Here we need 2 types of tests:

  • Controller tests - test the functionality of the controller itself
  • View tests - test the ability to render different views

 

In Spring 3.2 there's a new bean called mock MVC which is based on another open source project called spring test mvc: https://github.com/SpringSource/spring-test-mvc. This latter project was merged into Spring 3.2, but if you're on Spring 3.1 or earlier you can use that external project instead. They are almost identical, the main difference is that the package names changed. This spring test extension provides a fluent API for testing controllers.

 

Testing the controller means that we need to create a web application context (as opposed to an application context) and define all the web beans: content negotiation manager, message converters and our mock service. This can be done as follows:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class PersonControllerTest {
    @Autowired
    private PersonController controller;                  // the controller under test
    @Autowired
    private PersonService service;                        // the mock service
    private MockMvc mockMvc;                              // the mock Spring MVC fluent bean

    @PostConstruct
    public void setup() {
        // create the mock Spring MVC fluent bean
        mockMvc = MockMvcBuilders.standaloneSetup(controller)  // create it only for our controller
                // add the message converters
                .setMessageConverters(new MappingJackson2HttpMessageConverter(), new StringHttpMessageConverter())
                // and build it
                .build();
    }

    @Test
    public void json() throws Exception {
        // setup expectations on the service mock
        when(service.readAllPersons()).thenReturn(Arrays.asList(new Person("Moshe", "Cohen"), new Person("Yakov", "Levi")));

        // perform the request
        mockMvc.perform(get("/persons").accept(MediaType.APPLICATION_JSON))         // get /persons, accept json
                .andExpect(status().isOk())                                         // expect the status to be ok
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))       // expect json result
                .andExpect(jsonPath("$.", filter(where("firstName").is("Moshe"))).exists())  // expect Moshe in the list
                .andExpect(jsonPath("$..[?(@.firstName == 'Moshe')]").exists());    // same same (but different syntax)
    }


    // more tests here

     
    @Configuration
    @ComponentScan(basePackages = "com.hp.example.controllers")
    public static class PersonControllerTestConfiguration {
        @Bean
        public PersonService personService() {
            return mock(PersonService.class);
        }
    }
}

 

As you can see the mock MVC fluent bean is created in the post construct method by passing it the controller created in our web application context. Also the message converters are registered to it. Then the mock MVC is used in the test method in a very human readable way.

 

Notice the jsonPath matcher which uses the json-path library as a json parser and matcher: https://code.google.com/p/json-path/.

 

Finally - here's the test of the different view renderers. This test just verifies that the response is in the expected media type. Unlike the individual controller test above, this test is defined with the application's rest-servlet.xml:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration                // make this a web application context test
public class PersonViewTest {
    @Autowired
    private WebApplicationContext wac;
    @Autowired
    private PersonService service;
    private MockMvc mockMvc;

    @PostConstruct
    private void setup() {
        mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
    }

    @Test
    public void csv() throws Exception {
        when(service.readAllPersons()).thenReturn(Arrays.asList(new Person("Moshe", "Cohen"), new Person("Yakov", "Levi")));

        getPersonsForMediaType("text/csv");
    }

    @Test
    public void excel() throws Exception {
        when(service.readAllPersons()).thenReturn(Arrays.asList(new Person("Moshe", "Cohen"), new Person("Yakov", "Levi")));

        getPersonsForMediaType("application/vnd.ms-excel");
    }

    private MvcResult getPersonsForMediaType(String mediaType) throws Exception {
        return mockMvc.perform(get("/persons").accept(MediaType.parseMediaType(mediaType)))
                .andExpect(status().isOk())
                .andExpect(content().contentType(mediaType)).andReturn();
    }

    @Configuration
    @ImportResource("WEB-INF/rest-servlet.xml")      // import the bean definitions from the XML
    public static class PersonViewTestConfiguration {
        @Bean
        public PersonService personService() {
            return mock(PersonService.class);
        }
    }
}

 

Leave a Comment

We encourage you to share your comments on this post. Comments are moderated and will be reviewed
and posted as promptly as possible during regular business hours

To ensure your comment is published, be sure to follow the Community Guidelines.

Be sure to enter a unique name. You can't reuse a name that's already in use.
Be sure to enter a unique email address. You can't reuse an email address that's already in use.
Type the characters you see in the picture above.Type the words you hear.
Search
About the Author


Follow Us
The opinions expressed above are the personal opinions of the authors, not of HP. By using this site, you accept the Terms of Use and Rules of Participation