Thursday, January 19, 2006

Testing Controllers With Mocks

One of the reasons for setting up this page was I wanted to write something somewhere about the new testing techniques I've been using over the past few days. I'm working on a Java web application, using a fairly standard MVC structure. Working on an XP team, I've always felt a little uneasy about not having a good way to build controllers test first. Typically in past projects I've tested controllers indirectly by using tools like HttpUnit to interact with the web front end, and exercise the view and controller components together. This approach has a number of drawbacks. In the case of a failure, it is difficult to determine whether the bug is in the view or the controller (or even the model). It is also necessary to develop the view and the controller in tandem, as one cannot be tested without the other.

On my current project, we are using Spring to provide the MVC framework. This allows us to make use of some of the other facilities provided by Spring to enhance our testing.

Spring provides support for unit testing controllers independently. This has a number of advantages:

The difficulty we had had with unit testing controllers previously was that the methods in their public interface tended to take an HttpServletRequest and HttpServletResponse as parameters, and operate on these. When running a unit test, such objects are not normally to hand, and are difficult to construct. Spring aids this situation by providing the classes MockHttpServletRequest and MockHttpServletResponse in spring-mock.jar (note that these classes are not part of the core Spring jar). Here is an example of how we can use them in a JUnit test case.

import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.servlet.ModelAndView;

public MyControllerTest extends TestCase {

public void testGettingIndexPage() throws Exception {

MyController controller = new MyController();
controller.setIndexView("index.jsp");

MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletRequest();

request.setMethod("GET");

ModelAndView modelAndView = controller.handleRequest(request, response);

assertEquals("Should get index page", modelAndView.getViewName(), "index.jsp");
}
}


This test shows that we intend our controller to respond to requests on the base url to display index.jsp. By writing the test first, we can see what we need to implement in our controller. To make this test pass requires only a very simple controller (which will be a subclass of one of the Spring MVC controllers).

import org.springframework.web.servlet.mvc.AbstractController;

public class MyController extends AbstractController {

private String indexView;

public void setIndexView(String viewName) { indexView = viewName; }
public String getIndexView() { return indexView; }

public ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) {
return new ModelAndView(getIndexView());
}
}

To make things a bit more interesting, we can add a test that passes in some parameters on the request, and expects an appropriate response, for example passing in a search query and expecting a results page back. Adding to our test case, we can write:

public void testSearching() throws Exception {

MyController controller = new MyController();
controller.setSearchResultsView("results.jsp");
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
request.setMethod("GET");

request.addParameter("query","testing");

ModelAndView modelAndView = controller.handleRequest(request, response);

assertEquals("Should get results page", modelAndView.getViewName(), "results.jsp");
}

Again the intention of the test is pretty clear (we can now see that our testcase might benefit from factoring some common setup into a setup method, but we'll leave that for now), and again, we don't need to add much to our controller to make it pass.

public class MyController extends AbstractController {

// in addition to the fields and accessors from before...
private String searchResultsView;

public void setResultsView(String viewName) { searchResultsView = viewName; }
public String getSearchResultsView() { return searchResultsView; }

public ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) {

String query = request.getParameter("query");
if ( query != null ) {
// perform the search
return new ModelAndView(getSearchResultsView());
} else {
return new ModelAndView(getIndexView());
}
}
}

This shows a couple of very simple iterations of writing tests and implementation for a controller, you can go a lot further, making both the tests and the implementation more sophisticated. The main point is that we have built and tested the controller in a way that allows quick iteration, promotes clarity and good test coverage, and does not require a view component to be written to do so.





<< Home

This page is powered by Blogger. Isn't yours?