Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I get Spring MVC to invoke validation in a JUnit test?

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

like image 536
Matt Raible Avatar asked Sep 06 '12 20:09

Matt Raible


People also ask

How does a Spring Validator work?

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.


2 Answers

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.

like image 180
Solubris Avatar answered Sep 21 '22 05:09

Solubris


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);
like image 44
Biju Kunjummen Avatar answered Sep 23 '22 05:09

Biju Kunjummen