简体   繁体   English

如何让Spring MVC在JUnit测试中调用验证?

[英]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. 我有一个名为Browser的POJO,我用Hibernate Validator注释进行了注释。

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. 我编写了以下单元测试,试图验证我的Controller方法是否捕获了验证错误。

@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: 这是我的Controller的add()方法:

@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. 我遇到的问题是结果永远不会有错误,所以就像@Valid没有得到认可。 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? 有没有人知道如何在使用JUnit进行测试时识别(和验证)@Valid?

Thanks, 谢谢,

Matt 马特

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. 相反,您构造并调用控制器映射到的URL。 Here is a good example of how to do this: http://rstoyanchev.github.com/spring-31-and-mvc-test/#1 以下是如何执行此操作的一个很好的示例: 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): POM(需要使用spring里程碑回购):

    <!-- 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. 注意:spring-mvc-test lib还没有生产就绪。 There are some gaps in the implementation. 实施中存在一些差距。 I think its planned to be fully implemented for spring 3.2. 我认为计划在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. 让它工作的方法是通过Spring MVC堆栈调用控制器 - 有几种方法可以做到这一点,我觉得最好的方法是使用spring-test-mvc ,这提供了一个很好的机制通过堆栈调用。

Another way to call through the stack is to inject in HandlerAdapter to the test this way: 调用堆栈的另一种方法是以这种方式将HandlerAdapter注入到测试中:

@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);

Basically you instantiated a POJO with this.controller = new MyController() , then called its method this.controller.add(...) . 基本上你用this.controller = new MyController()实例化一个POJO,然后调用它的方法this.controller.add(...) Just simple Java with a simple object, without any context : @Valid is not taken into account. 只是带有简单对象的简单Java,没有任何上下文:@Valid不被考虑在内。

@ContextConfiguration will just load your possible beans, with possible custom validators and such, but it won't do the magic of processing @Valid. @ContextConfiguration将加载你可能的bean,可能有自定义验证器等,但是它不会像处理@Valid那样神奇。

What you need is something to emulate a request to the controller's add method. 您需要的是模拟对控制器的add方法的请求。 Completely emulate it, validation included. 完全模拟它,包括验证。 You were not far from doing so, since you used some Spring testing facilities (to instantiate a MockHttpServletRequest). 你离这样做不远,因为你使用了一些Spring测试工具(来实例化一个MockHttpServletRequest)。

If you use Spring 3.0.x or less , you need to do 如果您使用的是Spring 3.0.x或更低版本 ,则需要执行此操作

new AnnotationMethodHandlerAdapter()
      .handle(request, new MockHttpServletResponse(), this.controller);

to make it work. 使它工作。

If you use Spring 3.1+ , the above solution won't work ( see this link for more info ) ! 如果您使用的是Spring 3.1+ ,则上述解决方案将无效( 请参阅此链接以获取更多信息 )! You will need to use this library (from Spring team, so it's sound don't worry), while waiting for them to integrate it in next Spring version. 你需要使用这个库 (来自Spring团队,所以听起来不用担心),同时等待它们在下一个Spring版本中集成它。 Then you will have to do something like 然后你将不得不做类似的事情

myMockController = MockMvcBuilders.standaloneSetup(new MyController()).build();
myMockController.perform(get("/browser/create")).andExpect(...);

Also have a look at these very interesting slides from Rossen Stoyanchev (the part we are talking about here begins at slide #116) ! 还可以看看Rossen Stoyanchev的这些非常有趣的幻灯片 (我们在这里讨论的部分从幻灯片#116开始)!

Note : I won't enter in the debate of whether or not this kind of testing is considered as unit testing or integration testing. 注意:我不会参与辩论这种测试是否被视为单元测试或集成测试。 Some would say this is rather integration testing we are doing here, since we emulate the full path of a request. 有人会说这是我们在这里进行的集成测试,因为我们模拟了请求的完整路径。 But on another hand you can still mock your controller with like @Mock annotations from Mockito (or do similar stuff with any other mocking framework), so some others would say that you can reduce the scope of the test to almost pure "unit testing". 但另一方面,您仍然可以使用Mockito的@Mock注释来模拟您的控制器(或者使用任何其他模拟框架执行类似的操作),因此其他一些人会说您可以将测试范围缩小到几乎纯粹的“单元测试” 。 Of course you can alternatively and purely unit test your controller with plain old Java + a mocking framework, but in this case this won't allow you to test the @Valid validation. 当然,您也可以使用普通的旧Java +模拟框架对您的控制器进行单元测试,但在这种情况下,这将不允许您测试@Valid验证。 Make your choice ! 做出你的选择 ! :) :)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM