Skip to main content

Unit testing your Spring-MVC applications

Posted by johnsmart on February 3, 2008 at 7:15 PM PST

Spring-MVC might use the old MVC model rather than the more recent component-based approachs. It doesn't come with lots of AJAX-based components. It doesn't come with its own arcane tag library to learn - you have to content yourself to JSP/JSTL, Velocity, or FreeMarker. However, it is still a powerful and flexible (and fairly popular) choice as far as web frameworks go.

One of the great things about this framework is how testable it is. In Spring-MVC, any custom validators (for field and form validation) and property editors (for converting text fields to specific Java types) are dead-easy to test - you can just test them as if they where isolated POJOs. For example, here is how you could test a simple property editor, called FrequencyEditor, that converts a string into an enum value (called RepaymentFrequency):

public class FrequencyEditorTest {

    FrequencyEditor editor = new FrequencyEditor();
   
    @Before
    public void init() {
        editor = new FrequencyEditor();
    }
   
    @Test
    public void testSetValidValue() {
        editor.setAsText("MONTHLY");
        RepaymentFrequency value = (RepaymentFrequency) editor.getValue();
        assertEquals(RepaymentFrequency.MONTHLY, value);
    }

    @Test
    public void testGetValidValue() {
        editor.setValue(RepaymentFrequency.MONTHLY);
        assertEquals("MONTHLY", editor.getAsText());
    }
    ...

Pretty simple, eh? It gets better. Spring-MVC also comes with a full set of mock objects that you can use (with a bit of practice) to test your controllers to your heart's content. For example, you can use classes like MockHttpServletRequest and MockHttpServletResponse to simulate your HTTP request and response objects. This is also made easier by the fact that Controllers can be instanciated as normal Java classes. For example, imagine you are testing a controller class for a page that updates a client details record. You could do this very simply as follows:

public class UpdateClientTest {
        //
        // Prepare your request
        //
        request.setMethod("POST");     
        request.setParameter("id", "100");
        request.setParameter("firstName", "Jane");
        request.setParameter("lastName", "Doe");
        //
        // Invoke the controller
        //
controller = new ChoosePeriodController();
        ModelAndView mav = controller.handleRequest(request, response);
//
// Inject any service objects you need
//
        controller.setClientService(clientService);
...
        //
        // Inspect the results
        //
        assert mav != null;
        assertEquals("displayClient",mav.getViewName()); 
        Client client = (Client) mav.getModel().get("client");
        assertEquals("Jane",client.getFirstName()); 
        assertEquals("Doe",client.getLastName()); 
...       
    }
    ...

The implications of this are that you can do very thorough testing on all the Java layers of your application, from the controller layer down. Any you can choose the depth of your tests. If you just want fast, light-weight unit tests, you might also mock out the service classes (using something like JMock, for example). Or you can instanciate the Spring container and obtain the service object from there, which potentially lets you test the whole application (this is more integration testing than unit testing).

It can get hairy when there are lots of parameters, but, on the other hand, maybe this is when the testing becomes really useful.

All this is also true for Spring Portlet MVC, which has a similar set of mock objects. In fact, it is probably even more useful for portlet testing, since automated portlet user interface testing is very hard to script (the URLs tend to be dynamic and unpredictable).

That's the sort of thing that is totally lacking in JSF, for example. In Struts, you have something similar, but less powerful, in the form of StrutsTestCase.

Comments

Yes, that's the sort of thing - looks like an interesting project. Thanks for the tip.

Have you looked at JSFUnit [1]? [1] http://labs.jboss.com/jsfunit/