Professional Java Development with the Spring Framework
As with any other component in your application, you will naturally want to unit test your controllers as well. While we've left discussion of testability until late in this chapter, the topic is very important, and testability is by no means an afterthought in the design of Spring MVC!
Spring provides a collection of mock classes that may be used to unit test your controllers. These mock classes are included in the Spring distribution and are located in the spring-mock.jar. There are mocks for the ServletContext, HttpServletRequest, HttpServletResponse, and other core Servlet API interfaces. These mocks go beyond the capabilities of those available from MockObjects.com, and thus are provided by Spring. (They are extensively used in testing Spring itself: Mock objects are used widely in Spring's own unit tests.)
You can test your controllers in a couple of ways, but directly instantiating the controllers and calling the controller's handle method is the easiest. To do more extensive testing of the controller's behavior (including verification of the calling of methods on business objects), you can use EasyMock. If your application context is more complex and the dependencies of your controller include components available only in the application context and hard to construct in a unit test, you might want to test the controllers after instantiating the application context itself. We'll cover both approaches in the following examples.
Testing Without the Application Context
In the following example we'll assume the following:
-
We want to test the ViewShowController, as explained in the first part of the usage scenario section.
-
We've implemented a dummy of the BoxOffice interface, delivering data we can use for testing purposes. This prevents us from having to create a database.
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.sample.boxoffice.DummyBoxOffice; public class ViewShowControllerTest extends TestCase { private ViewShowController vsc; public void setUp() { vsc = new ViewShowsController(); // use our previously create MockBoxOffice vsc.setBoxOffice(new DummyBoxOffice()); } public void testCorrectModel() { // construct request and add the parameter for the show id MockHttpServletRequest request = new MockHttpServletRequest("GET", "/listshows.html"); request.addParameter("id", "1"); // construct the response MockHttpServletResponse response = new MockHttpServletResponse(); // retrieve the model ModelAndView mav = vsc.handleRequest(request, response); // and do some tests assertEquals(1, mav.getModel().size()); assertEquals(Show.class, mav.getModel().get("show")); } public void testNoShowId() { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/listshows.html"); // no parameter in this test, since we want to test if the // model will contain a correct error message MockHttpServletResponse response = new MockHttpServletResponse(); ModelAndView mav = vsc.handleRequest(request, response); assertEquals(1, mav.getModel().size()); assertEquals("unexpected.noshowid", mav.getModel().get("message")); // the correct message was available } }
More Extensive Testing Using Mock Objects
Remember that in the previous example we're constructing the controller ourselves, so we don't actually test the behavior of the complete configuration, including, for example, HandlerMappings and the DispatcherServlet. Another way of testing would be to use a mock objects library such as EasyMock (www.easymock.org) to test if the controller actually performs as expected. We won't explain EasyMock in detail here; however, the following example should give you a decent enough idea. We'll modify the test shown in the previous example to include verification of the actual calls to the BoxOffice object:
import org.easymock.MockControl; public class ViewShowControllerTest extends TestCase { public void testCorrectModel() { // construct the mockcontrol, the mock MockControl control = MockControl.createControl(BoxOffice.class); BoxOffice office = (BoxOffice)control.getMock(); // and the controller ViewShowController controller = new ViewShowController(); Controller.setBoxOffice(office); // record the (expected) behavior office.getShow(1); control.setReturnValue(new Show()); // finished recording, now replay and execute the // appropriate method control.replay(); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/listshows.html"); request.addParameter("id", "1"); MockHttpServletResponse response = new MockHttpServletResponse(); ModelAndView mav = vsc.handleRequest(request, response); assertEquals(1, mav.getModel().size()); assertEquals(Show.class, mav.getModel().get("show")); // verify the calling sequence (is the getShow() method actually called) control.verify(); } }
EasyMock, which we used in this example, is just one of several mock object libraries available. Others include jMock, which can be found at http://jmock.codehaus.org.
Testing Using the Complete Application Context
There are two reasons for testing controllers with a completely instantiated application context. First, it might be too difficult or complex to test a component in complete isolation. While you should always try to mock every collaborator a component has, in some cases it might take too much time or be too much of a hassle. The second reason is to test the application context itself. Personally, we feel the latter shouldbe done while executing an integration test only. Testing using an application context might result in strange behavior. It's not just your controllers (or whatever components) you're testing; you're testing a complete application.
If you need to test using a concrete application context, you have to start using the org.springframework.web.context.support.XmlWebApplicationContext directly. Use the context in combination with the org.springframework.mock.web classes geared toward testing Servlet API–dependent components without a servlet container. Remember that not all functionality of a servlet container will be available. Rendering JSPs, for example, will not be possible! First instantiate an org.springframework.mock.web.MockServletContext and pass it to the XmlWebApplicationContext. The context will be loaded from the resourceBasePath specified while constructing the MockServletContext, although omitting it will also work (the classpath will be used as your WAR root). Any additional application context files you normally specify using the contextConfigLocations initialization parameter in the web.xml file will now have to be specified using the XmlWebApplicationContext programmatically:
MockServletContext servletContext = new MockServletContext("./war"); XmlWebApplicationContext webContext = new XmlWebApplicationContext(); webContext.setServletContext(servletContext); // initialize parent contexts (e.g. applicationContext.xml) ApplicationContext context = ... webContext.setParent(context); // results in servletname-servlet.xml being loaded from WEB-INF webContext.setNamespace("servletname"); webContext.refresh();
After you've initialized your web application context, it's a matter of getting your controller bean and executing a request:
Controller controller = (Controller)webContext.getBean("controller"); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/listShows.html"); MockHttpServletResponse response = new MockHttpServletResponse(); ModelAndView mav = controller.handleRequest(request, response); // do asserts
Note that Spring includes some very useful abstract test classes that facilitate testing of Spring contexts. They are located in the spring-mock.jar that comes with the distribution. One of those is the org.springframework.test.AbstractSpringContextTests. This class does not support the loading of WebApplicationContexts as shown above when using it out-of-the-box. However, you can easily extend it and load the WebApplicationContext yourself. One important advantage of the Spring test support classes (besides the fact that they eliminate the need for you to write lookup code) is that some of them cache the application contexts across multiple test methods. This can significantly speed up the running of tests, improving productivity. It is possible that web-specific test superclasses will be added to the org.springframework.test package in future releases of Spring.
Other Ways to Test a Web Application
While testing units of your application in the most isolated form as possible is good practice, executing integration tests is also vital. JUnit in its raw form does not offer a convenient way to test static or dynamic websites as a whole. To do such HTTP-based integration tests, you can choose from several tools:
-
jWebUnit and HttpUnit are both hosted on SourceForge and offer JUnit extensions to easily set up a web conversation and test the flow of a web application programmatically.
-
TestMaker is created by Push2Test (www.pushtotest.com/ptt/testmaker) and offers an advanced application to perform testing of web applications by using a scripting language.
-
Canoo WebTest (http://webtest.canoo.com/) uses an XML syntax to express expected behavior of a web application.
We recommend using automated testing techniques when building web interfaces. In general, as much testing as possible should be automated, in all layers of an application. Automated testing of web interfaces is particularly valuable in that it gives you a strong set of regression tests, eliminating the need for tedious (and error-prone) clicking through pages to verify that a change hasn't broken previous functionality.