Thanks to Spring’s new MVC Annotations (@Controller, @RequestMapping, @RequestParam etc.) unit testing a Spring controller is now easier than ever before. Still, I had a hard time figuring out how to do this since most of your controller methods will not take a ServletRequest as input. Instead they use a form-backing / command object, a BindingResult and maybe a ModelMap. In a web environment Spring takes care of filling the command object and validation results however in a unit test you will have to do this yourself.

Following is the basic outline for writing such a test.

This StackOverflow post got me started originally.

Configuration

Make sure there is a validator configured somewhere in your test context, the corresponding bean could look like this:

<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />

In order for your controller to be picked up and injected into your test you will also need this in your config:

<context:component-scan base-package="the.package.your.controllers.are.in" />

I am using the Hibernate validators which provide a reference implementation of JSR-303 (Bean Validation) so you will need these dependencies in your pom.xml (if you use Maven):

<dependency>
  <groupId>javax.validation</groupId>
  <artifactId>validation-api</artifactId>
  <version>1.0.0.GA</version>
  <type>jar</type>
  <scope>compile</scope>
</dependency>
<dependency>
  <groupId>org.hibernate</groupId>
  <artifactId>hibernate-validator</artifactId>
  <version>4.0.2.GA</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>3.0.0.RELEASE</version>
</dependency>

Test

Let’s get down to business, here is what your unit test could look like:

public class ControllerTest {

  @Autowired
  private org.springframework.validation.Validator validator;
  @Autowired
  private YourController yourController;

  @Test
  public void testController() {

    MockHttpServletRequest request = new MockHttpServletRequest("POST", "/edit");

    // populate the request, use the same names as you would for the form elements in the corresponding .jsp
    request.setParameter("text", "Some text");

    // create a new instance of your form object
    YourFormObject formObject = new YourFormObject();

    WebDataBinder binder = new WebDataBinder(formObject);
    binder.setValidator(validator); // use the validator from the context
    binder.bind(new MutablePropertyValues(request.getParameterMap()));

    // validation must be triggered manually
    binder.getValidator().validate(binder.getTarget(), binder.getBindingResult());

    // if you want to test if the validation works as expected you can check the binding result
    // eg. if no errors are expected:
    assertEquals(0, binder.getBindingResult().getErrorCount());

    // now call your controller method and store the return type if it matters
    // f. ex. a different view might be returned depending on whether there are validation errros
    String view = yourController.edit(binder.getTarget(), binder.getBindingResult());

    assertEquals("error", view);

    // now you can test the target object, f. ex.:
    assertEquals("Some text", binder.getTarget().getText());
  }
}

Conclusion

Spring rocks 🙂

As you can see there is a lot of boilerplate code (creating the binder, binding, validation) that could easily be moved to a private method. More importantly there are two things which I have not really figured out yet:

  1. I need to explicitly call a method on the controller instead of having it figure out by itself which method to use depending on the request method and URL. Of course this would make building and passing in the command object impossible so I guess a viable option is to find which Spring component is responsible for mapping URLs to controller methods and test these mappings explicitly.
  2. An @ExceptionHandler has to be called manually. This stems from the fact that if the controller method throws an exception it is passed on to the test which will then fail if it does not catch the exception. Therefore the exception never reaches the exception handler. If you think about it that makes sense – the automatic calling of the @ExceptionHandler would be useful in an integration test. However in a unit test catching the (expected) exception from the controller method is sufficient as you only want to test that unit.