For some reason this is terribly documented on the web .. so, I hope this helps someone.
Assuming we have the following model:
public class PersonForm {
@NotNull
@Max(64)
private String name;
@Min(0)
private int age;
}
To perform more advanced validation, we could write a custom validator class:
public class PersonFormValidator implements Validator {
/*
* This Validator validates *just SomeModel instances
*/
public boolean supports(Class clazz) {
return PersonForm.class.equals(clazz);
}
public void validate(Object obj, Errors e) {
PersonForm personForm = (PersonForm) obj;
// for the purpose of this demo
// we're going to reject this value all the time
e.rejectValue("age", "age.rejected");
}
}
Notes:
- Notice that our validator
implements Validator
- So it also has to implement the methods:
supports
andvalidate
- So it also has to implement the methods:
- We use
e.rejectValue("<field>", "<message>");
to reject this field
Finally, our controller might look something like this:
@Controller
public class PersonController{
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.setValidator(new PersonFormValidator());
}
@RequestMapping(value = "/person", method = RequestMethod.POST)
public String submitPerson(
@Valid @ModelAttribute("PersonForm")
PersonForm personForm,
BindingResult binding,
HttpSession session,
RedirectAttributes redirectAttributes) {
//validation
if(binding.hasErrors()){
return "/pages/person";
}
return "/pages/personSuccess";
}
}
Notes:
@IinitBinder
sets the validator for this@Controller
to be ourPersonFormValidator
.- Remember:
supports(Class clazz)
inPersonFormValidator
tells the controller which models are validated with this validator
- Remember:
- The line:
@Valid @ModelAttribute("PersonForm") PersonForm personForm
tells the controller to validate thePersonForm
object. Results from the validation are stored inBindingResult binding
- The
@Valid
annotation will validate our modelPersonForm
using the annotations within the class (e.g.:@NotBlank
,@min
and@max
as well as running the validation defined inPersonFormValidator.validate()
- Finally, we check if there are errors and deal with them accordingly with
binding.hasErrors()
Making sure it all works
import static org.springframework.test.web.server.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.server.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.view;
public class PersonControllerTest {
MockMvc mockMvc;
@InjectMocks
PersonController controller;
@Before
public void setUp() throws Exception {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/jsp/view/");
viewResolver.setSuffix(".jsp");
this.mockMvc = MockMvcBuilders.standaloneSetup(controller)
.setViewResolvers(viewResolver)
.setMessageConverters(new MappingJackson2HttpMessageConverter()).build();
}
@Test
public void testSubmitPerson_failsValidation()
throws Exception {
this.mockMvc.perform(
post("/person")
.param("name", "Joe")
.param("age", "100")
).andExpect(model()
.attributeHasFieldErrors("personForm", "age"))
.andExpect( view().name("/pages/person") )
.andExpect( status().isOk() )
.andDo(print());
}
}
Notes:
- Standard setup for mockMvc in
setup()
.andExpect(model().attributeHasFieldErrors("personForm", "age"))
does most of the work here - it ensures that the "age" field on our "personForm" has an error raised against it.- I included the imports for the various Builders, Matchers and Handlers .. cause, for some reason, they're often a little tricksy to find in my IDE (might be the same for you ;) ).
.. and that should do the trick :).
In summary
- Create a model (
PersonForm
) - Create a custom validation class for validation our model (
PersonFormValidator
) - In your
@Controller
, bind your validator to the controller using the@InitBinder
annotation andbinder.setValidator
- in your
@Controller
method, Use the@Valid
annotation to validate the incoming model. - You can test the results with the
MockMvc
andmodel().attributeHasFieldErrors()
to ensure that validation fails in the correct cases.
References:
- Spring MVC 3 Validation
- MockMVC to test Spring MVC form validation
- Gist: Example using attributeHasFieldErrors and attributeHasNoError