Thursday 4 February 2010

Unit testing Spring Controllers

I found this DevX blog by Edmon Begoli really useful for writing unit tests for Spring controllers using MockHttpRequest and MockHttpResponse. However, I think the ideas in it can be extended slightly to help those using Spring Security and Hibernate, particularly the Open Session In View pattern, perhaps aided by Spring's OpenSessionInViewFilter.

My first enhancement would be to load the Controller instances from Spring rather than assembling them by hand. The key to doing this with Maven is to move the "spring-servlet.xml" file that configures the controllers from the src/main/webapp/WEB-INF folder in to src/main/resources. This doesn't cause any problems as you can load the spring-servlet.xml from anywhere on the classpath.


  spring
    org.springframework.web.servlet.DispatcherServlet
    
       contextConfigLocation
       classpath:spring-servlet.xml  
     
  1

Having done this, you can then load the spring-servlet.xml application context in your Controller unit tests.

The setUp() method below shows how to do this, in addition to obtaining a reference to the Spring security AuthenticationManager and starting a Hibernate Session. The latter allows us to replicate the effects of the OpenSessionInViewFilter while the former gives us the means to log in to Spring Security before executing a test.

protected void setUp() throws Exception {
  super.setUp();
  
  // Set up Spring application context
  String[] paths = { "application-context-datasource-test.xml",
       "application-context-dao.xml",
       "application-context-facade.xml",
       "application-context-mail.xml",
       "spring-servlet.xml" };
  ctx = new ClassPathXmlApplicationContext(paths);
      
  // Get Spring AuthenticationManager
  am = (AuthenticationManager)ctx.getBean("authenticationManager");  
       
  // Create mock request and response
  request = new MockHttpServletRequest();
  response = new MockHttpServletResponse();
  
  // Start session (simulate open session in view)
  factory = (SessionFactory)ctx.getBean("sessionFactory");
  session = sessionFactory.openSession();
  TransactionSynchronizationManager.bindResource(factory, new SessionHolder(session));
}

A simple "log in" method can be provided for use in the tests.

protected void login(String username, String password) {
  Authentication auth = am.authenticate(new UsernamePasswordAuthenticationToken(username, password));
  SecurityContextHolder.getContext().setAuthentication(auth);
}

A sample test method might look like this.

public void testStuff() throws Exception {
  // Get the controller
  MyController ctrlr = (MyController ) ctx.getBean("myController");

  // Login as manager
  login("bill.business-manager", "secret");

  // Controller renders form on GET method
  request.setMethod(AbstractController.METHOD_GET);

  // Execute the request
  ModelAndView mav = ctrlr.handleRequest(request, response);
  
  // Test stuff
  assertNotNull(mav);
}

No comments:

Post a Comment