Unit Testing a Spring @Controller
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:
- 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.
- 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.
Thanks for the post. I am new to testing spring mvc controllers and I was having trouble with the validator. It is always null. I have tried @Autowired private Validator validator. Can you describe how to set up the test-context?
Thanks
Billy,
this was ages ago so I’m not sure what I did there 🙂 Have you tried to explicitly configuring the validator like I described instead of @Autowiring it?