I have a POJO called Browser that I've annotated with Hibernate Validator annotations.
import org.hibernate.validator.constraints.NotEmpty;
public class Browser {
@NotEmpty
private String userAgent;
@NotEmpty
private String browserName;
...
}
I've written the following unit test that tries to verify my Controller method catches validation errors.
@Test
public void testInvalidData() throws Exception {
Browser browser = new Browser("opera", null);
MockHttpServletRequest request = new MockHttpServletRequest();
BindingResult errors = new DataBinder(browser).getBindingResult();
// controller is initialized in @Before method
controller.add(browser, errors, request);
assertEquals(1, errors.getErrorCount());
}
Here's my Controller's add() method:
@RequestMapping(value = "/browser/create", method = RequestMethod.POST)
public String add(@Valid Browser browser, BindingResult result, HttpServletRequest request) throws Exception {
if (result.hasErrors()) {
request.setAttribute("errorMessage", result.getAllErrors());
return VIEW_NAME;
}
browserManager.save(browser);
request.getSession(false).setAttribute("successMessage",
String.format("Browser %s added successfully.", browser.getUserAgent()));
return "redirect:/" + VIEW_NAME;
}
The problem I'm experiencing is that result never has errors, so it's like @Valid isn't getting recognized. I tried adding the following to my test class, but it doesn't solve the problem.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"file:path-to/WEB-INF/spring-mvc-servlet.xml"})
Does anyone know how I'd get @Valid to be recognized (and validated) when testing with JUnit?
Thanks,
Matt
Spring features a Validator interface that you can use to validate objects. The Validator interface works using an Errors object so that while validating, validators can report validation failures to the Errors object.
The validation is done before the call to the controller, so your test is not invoking this validation.
There is another approach to testing controllers, where you dont invoke the controller directly. Instead you construct and call the URL that the controller is mapped on. Here is a good example of how to do this: http://rstoyanchev.github.com/spring-31-and-mvc-test/#1
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader=WebContextLoader.class, locations = {"classpath:/META-INF/spring/applicationContext.xml", "classpath:/META-INF/spring/applicationContext-test-override.xml", "file:src/main/webapp/WEB-INF/spring/webmvc-config.xml"})
public class MyControllerTest {
@Autowired
WebApplicationContext wac;
MockMvc mockMvc;
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webApplicationContextSetup(this.wac).build();
}
@Test
@Transactional
public void testMyController() throws Exception {
this.mockMvc.perform(get("/mycontroller/add?param=1").accept(MediaType.TEXT_HTML))
.andExpect(status().isOk())
.andExpect(model().attribute("date_format", "M/d/yy h:mm a"))
.andExpect(model().attribute("myvalue", notNullValue()))
.andExpect(model().attribute("myvalue", hasSize(2)))
.andDo(print());
}
}
POM (need to use spring milestone repo):
<!-- required for spring-test-mvc -->
<repository>
<id>spring-maven-milestone</id>
<name>Spring Maven Milestone Repository</name>
<url>http://maven.springframework.org/milestone</url>
</repository>
...
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test-mvc</artifactId>
<version>1.0.0.M1</version>
<scope>test</scope>
</dependency>
NOTE: the spring-mvc-test lib is not production ready yet. There are some gaps in the implementation. I think its planned to be fully implemented for spring 3.2.
This approach is a great idea as it tests your controllers fully. Its easy to mess up your controller mappings, so these do really need to be unit tested.
Validators are called ahead of the controller methods being invoked - during the process of binding the request to the method parameters. Since in this case you are invoking the controller method directly, the binding and the validation steps are being bypassed.
The way to get it to work will be to make the call to the controller through the Spring MVC stack - There are a few ways to do this, I feel the best way is to use spring-test-mvc which provides a nice mechanism to call through the stack.
Another way to call through the stack is to inject in HandlerAdapter to the test this way:
@Autowired
private RequestMappingHandlerAdapter handlerAdapter;
Then in the test:
MockHttpServletRequest request = new MockHttpServletRequest("POST","/browser/create");
MockHttpServletResponse response = new MockHttpServletResponse();
httpRequest.addParameter(.... );//whatever is required to create Browser..
ModelAndView modelAndView = handlerAdapter.handle(httpRequest, response, handler);
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