I have written a very simple web controller and a very simple unit test with MockMvc.
The web controller returns a DTO of the ScanRequestDTO type to the view with which it populates a form's data.
This mapping is done with the @ModelAttribute tag in the relevant controller method.
When doing the test I want to ensure that am object of the correct type has been supplied in the response to the get request.
However my test continually returns:
Model attribute 'scanRequestDTO' expected: but was:<ScanRequestDTO(name=null, dateOfBirth=null, nationality=null, idNumber=null)>
I am expecting it to be the ScanRequestDTO so I can't work out why the test is expecting null.
The relevant code is:
Controller
@GetMapping("/doscan")
public String doScan(@ModelAttribute("scanRequestDTO") ScanRequestDTO scanRequest, Model model)
{
checkSubmittedData(scanRequest);
return "doScan";
}
Unit Test
@Test
void doScanTest() throws Exception {
this.mockMvc.perform(get("/web/doscan"))
.andExpect(model().attribute("scanRequestDTO", any(ScanRequestDTO.class)))
.andExpect(view().name("doScan"))
.andExpect(status().isOk());
}
The error is coming from .andExpect(model().attribute("scanRequestDTO", any(ScanRequestDTO.class)))
I expect it to pass the test as it is returning a ScanRequestDTO
class as expected.
However the test is expecting it to return a null and I cannot for the life of me work out why.
This is due to wrong import of any
org.mockito.ArgumentMatchers.any
The correct one should be
org.hamcrest.Matchers.any
Although the two looks very similar, but how they works are quite different.
Mockito one is specifically created for Mockito, while Hamcrest one is widely used in different library(including Mockito)
Such misleading error is because there is overloading in ModelResultMatchers
public <T> ResultMatcher attribute(String name, Matcher<? super T> matcher) {
return (result) -> {
ModelAndView mav = this.getModelAndView(result);
MatcherAssert.assertThat("Model attribute '" + name + "'", mav.getModel().get(name), matcher);
};
}
public ResultMatcher attribute(String name, @Nullable Object value) {
return (result) -> {
ModelAndView mav = this.getModelAndView(result);
AssertionErrors.assertEquals("Model attribute '" + name + "'", value, mav.getModel().get(name));
};
}
Hence when we imported Mockito's any
method, the return type will be ScanRequestDTO
, which does not match Matcher<? super T>
. So we are actually calling attribute
with Object value
. And since Mockito any
is returning null, the assertion become asserting the attribute to be null.