Sunday, July 1, 2012

Spring MVC Integration Tests

An approach to Integration Testing the controllers in Spring MVC is to use the Integration Test support provided by Spring.

With Junit4 this support consists of a custom Junit Runner called the SpringJunit4ClassRunner, and a custom annotation to load up the relevant Spring configuration.

A sample Integration test would be along these lines:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:/META-INF/spring/webmvc-config.xml", "contextcontrollertest.xml"})
public class ContextControllerTest {

    @Autowired
    private RequestMappingHandlerAdapter handlerAdapter;

    @Autowired
    private RequestMappingHandlerMapping handlerMapping;
    
    ......
 
 @Test
 public void testContextController() throws Exception{
  MockHttpServletRequest httpRequest = new MockHttpServletRequest("POST","/contexts");
  httpRequest.addParameter("name", "context1");
  
  httpRequest.setAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE,new FlashMap());
  MockHttpServletResponse response = new MockHttpServletResponse();
  Authentication authentication = new UsernamePasswordAuthenticationToken(new CustomUserDetails(..), null);
  SecurityContextHolder.getContext().setAuthentication(authentication);

  Object handler = this.handlerMapping.getHandler(httpRequest).getHandler();
  ModelAndView modelAndView = handlerAdapter.handle(httpRequest, response, handler);
  assertThat(modelAndView.getViewName(), is("redirect:/contexts"));
 }
}


I have used a MockHttpServletRequest to create a dummy POST request to a "/contexts" uri, and added some authentication details for Spring Security related details to be available in the Controller. The ModelAndView returned by the controller is being validated to make sure the returned view name is as expected.

A better way to perform a Controller related integration is using a relatively new Spring project called Spring-test-mvc , which provides a fluent way to test the controller flows. The same tests as above look like the following with Spring-test-mvc:
@Test
public void testContextController() throws Exception{
 Authentication authentication = new UsernamePasswordAuthenticationToken(new CustomUserDetails(..), null);
 SecurityContextHolder.getContext().setAuthentication(authentication);
 
 xmlConfigSetup("classpath:/META-INF/spring/webmvc-config.xml", "classpath:/org/bk/lmt/web/contextcontrollertest.xml").build()
  .perform(post("/contexts").param("name", "context1"))
  .andExpect(status().isOk())
  .andExpect(view().name("redirect:/contexts"));
}

The test has now become much more concise and there is no need to deal directly with a MockHttpServletRequest and MockHttpServletResponse instances and reads very well.

I have a little reservation about the amount of static imports and the number of function calls that are involved here, but again like everything else it is just a matter of getting used to this approach of testing.

One issue that I faced when implementing this controller test is that any resources that are required in the Spring context files from the WEB-INF location is not available in unit tests, so I moved all my Web related application context files from `/WEB-INF/spring` folder to the `META-INF/spring` folder in the classpath. Further, there were some beans that are heavily dependent on WEB-INF resources, for eg, tiles layouts, so I moved out the Tiles related beans to a separate Spring context file which are not imported as part of the Integration tests.
I have posted another blog entry with how spring-test-mvc supports loading web resources.

3 comments:

  1. You should be able to reference files under the /WEB-INF/spring folder. Check the configureWebAppRootDir(String, boolean) method on the ContextMockMvcBuilder.

    ReplyDelete
    Replies
    1. Thanks Rossen, I have now fixed the entry

      Delete
  2. Very helpful.
    Thanks! for sharing such good information over integration testing.

    ReplyDelete