The Spring MVC Test Framework was incorporated into the Spring Framework in version 3.2. It allows tests to be run against the actual spring configuration and a real DispatcherServlet. This makes it great for testing container level configuration that would be impossible to unit test by simply instantiating one or two classes. As an example, here’s a recipe for testing annotation based bean validation in a Spring Web MVC project.
The source code for this is available from the Spanners project on GitHub. This demo code was added in version 2.4.
Setting up annotation based form validation
Form validation can be configured just by annotating the domain model with JSR-303 validation constraints:
public class SpannerForm { // Name may be 1-255 characters @Size(min=1, max=255) private String name; // Size may be 1-99 @Min(1) @Max(99) private int size; }
When the form is submitted to a Web MVC controller, we can tell Spring to validate the entered data by using the @Valid annotation:
@RequestMapping(value = "/editSpanner", method = RequestMethod.POST) public ModelAndView updateSpanner(@Valid @ModelAttribute(MODEL_SPANNER) SpannerForm formData, BindingResult validationResult) { if (validationResult.hasErrors()) { // formData is not valid return new ModelAndView(VIEW_VALIDATION_ERRORS); } // Otherwise, formData is valid }
The result of any validation failure can be shown in the HTML form using spring-form errors tag.
Aside from adding a few annotations to my domain model class and my controller class, I’ve not written a single line of Java code to implement this form validation. So if I want to test it, I’ll need to run my tests against the Spring context.
MockMVC
The MockMVC class allows tests to be run against a real Spring application context without actually having to run the complete application in a Servlet container. It’s fairly straightforward to set up:
@WebAppConfiguration @ContextConfiguration(classes = { // MVC application context to be tested WebMvcConfig.class, // Stubs for any services required for application context to start StubConfig.class }) @RunWith(SpringJUnit4ClassRunner.class) public class AddSpannerValidationTest { @Autowired protected WebApplicationContext wac; protected MockMvc mockMvc; @Before public void setup() throws Exception { // Set up a mock MVC tester based on the web application context mockMvc = webAppContextSetup(wac).build(); } }
In the above code, the MockMvc is setup against the WebApplicationContext as defined in the WebMvcConfig class. It could be set up just as easily against XML based application configuration:
@WebAppConfiguration @ContextConfiguration(locations={"classpath:/WEB-INF/springmvc-servlet.xml"}) @RunWith(SpringJUnit4ClassRunner.class) public class AddSpannerValidationTest { ... }
Note though, that we’re starting our test against the real application context, not a stub or an approximation. This means that all the goodies that Spring provides for us (such as request mappings, data binding and of course validation) are available and testable.
Testing controllers
A MockMvc based test will typically perform some action (GET or POST to a URL) and then make assertions on the response. Here’s a typical example of verifying a GET request to a URL mapped to a controller:
@Test public void testGetPage() throws Exception { // Logged in user requests page mockMvc.perform(get(CONTROLLER_URL).principal(CURRENT_USER)) // HTTP 200 returned .andExpect(status().isOk()) // Controller forwards to correct view .andExpect(view().name(VIEW_ADD_SPANNER)) // Spanner model attribute is set .andExpect(model().attributeExists(MODEL_SPANNER)); }
If any of the expectations are not met then the test will fail.
Testing validation rules
I can POST the form data to my controller and assert (expect) that validation errors are triggered:
/** * Assert that validation error is triggered if name field is left blank */ @Test public void testEmptyNameValidation() throws Exception { // Post to URL mapped to controller mockMvc.perform(post(CONTROLLER_URL) // User is logged in .principal(CURRENT_USER) // Spanner name is empty! .param("name", "") // Spanner size .param("size", 16)) .andExpect(view().name(VIEW_VALIDATION_FAIL)) .andExpect(model().attributeHasFieldErrors(MODEL_SPANNER, FIELD_NAME)); }
As my test class will repeatedly POST data to the same controller, I can abstract the form submission leaving just the andExpect clauses in my test:
/** * Post the add spanner form */ private MockHttpServletRequestBuilder postForm(String name, String size) { // Post to URL mapped to controller return post(CONTROLLER_URL) // User is logged in .principal(CURRENT_USER) // Spanner name .param("name", name) // Spanner size .param("size", size); } /** * Assert that validation error is triggered if size field is less than 1 */ @Test public void testMinSizeValidation() throws Exception { mockMvc.perform(postForm("Bertha", "0")) .andExpect(view().name(VIEW_VALIDATION_FAIL)) .andExpect(model().attributeHasFieldErrors(MODEL_SPANNER, FIELD_SIZE)); }
Hi Stevie,
I’ve recently started using MockMVC and have become a big fan. What I’ve noticed however is that the methods of my controller called using MockMVC do not show as covered in my Sonar unit test coverage. Have you encountered this, and do you have any idea what I can do about it?
Thanks,
Rob
Sorry Robert, that’s not a problem I’ve experienced myself. I am running Sonar to get test coverage stats but I can’t tell if my MockMVC tests contribute to coverage as I’m running regular unit tests on my controller classes as well as MockMVC tests as described above.
In my opinion, the above recipe is good for testing Spring container configuration but not so good for testing the logic in the Controller class. I’d still recommend writing a ‘regular’ unit test for that. The test would simply create an instance of the controller and call the updateSpanner() method. If you download the source code from GitHub (link at the top of the post), you’ll see that AddSpannerControllerTest does just that.
I appreciate that this does not answer your question but I hope it’s of use to you.
I currently believe the issue is actually due to the use of PowerMockRunner and not MockMVC.
give me spring test mvc war file of tested app….
Hi Sagar. This code was in version 2.4 of the Spanners app hosted on GitHub. You can also download the source zip and built war file from my Maven repo at http://www.disasterarea.co.uk/maven/org/dontpanic/spanners-mvc/2.4/.
Great, thank you! Regards from Germany.
Thank you so much, trying to do this for hours 🙂