I'm working on a simple website using Spring Boot and Spring MVC, and I've come up against a scenario for which I can't manage to write a test. How do you test how the Controller deals with Errors
, which affect the user interface, but aren't included in the ModelAndView
?
I was having trouble displaying error messages in my HTML form, and after much frustration I found it was because I was overwriting the Form object before returning it as an Object in the ModelAndView
.
This is the code in question
@PostMapping
ModelAndView processPost(@Valid IdAndAmountForm idAndAmountForm, Errors errors) {
if (errors.hasErrors()) {
var beer = service.findBeerById(idAndAmountForm.getId());
return new ModelAndView("beerView")
.addObject("beer", beer)
.addObject("idAndAmountForm", idAndAmountForm);
} else {
cart.add(idAndAmountForm);
return new ModelAndView("redirect:/cart");
}
}
It works / behaves as expected, populating the span
with the appropriate message when the HTML validation is removed.
<form th:action="@{/beer}" th:object="${idAndAmountForm}" method="post">
<label>
Amount
<span th:errors="*{amount}"></span>
<input th:field="*{amount}" type="number" min="1" required>
</label>
My mistake was in line 7 of the controller, where I previously had .addObject("idAndAmountForm", new IdAndAmountForm());
There were no errors associated with the new form, so no error messages were displayed. Or that's what I think happened.
Now how could I have written a test that detected this faulty behavior?
I spent some time dissecting the Model
, looking for the error messages that I knew were somehow being passed to the Thymeleaf template, before concluding that the error messages were being handled separately by Spring. This answer helped me to see it was the BindingResult
or Errors
object that I wanted to check in my test. However, it's not part of the return value of any method I wrote.
I suppose I could try to write a test for the ViewResolver
or whatever automatically generated object intermediates between my controller and my template, or I could butcher my controller class to allow my test class to access the Errors. Both seem like bad ideas. The current solution is to consider the scenario too much effort to test, but I don't like to believe that this is untestable in principle.
You can write a Controller test with Spring to achieve what you want. Spring MVC Test framework provides a really nice and easy to read fluent API which can be used to test all sort of scenarios in MVC using the MockMvc.
For your use case, testing for model attribute binding result errors, you will probably write a test which uses the MockMvcResultMatchers and the static status(), model(), view(), flash() etc
Take your time to explore these and see what methods you can call on them. For e.g in the model you have multiple methods related to the fieldErrors and you can be as precise as you want with the checks you do. In the end your test class will look like this:
mockMvc.perform(post(YOUR_URL).contentType(APPLICATION_FORM_URLENCODED)
.param(...)
.param(...)
.andExpect(model().hasErrors())
.andExpect(model().attributeHasFieldErrors(FORM_MODEL_NAME, "field"))
.andExpect(model().attributeHasFieldErrorCode(FORM_MODEL_NAME, "anotherfield", "error")
.andExpect(view().name(YOUR_VIEW));
More about the Spring MVC Test Framework here: https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html#spring-mvc-test-framework
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With