MockMVC to test Spring MVC form validation

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.

form validation message

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));
}

 

 

7 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *