15

我有一个名为 Browser 的 POJO,我用 Hibernate Validator 注释进行了注释。

import org.hibernate.validator.constraints.NotEmpty;

public class Browser {

    @NotEmpty
    private String userAgent;
    @NotEmpty
    private String browserName;

...

}

我编写了以下单元测试,试图验证我的 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());
}

这是我的控制器的 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;
}

我遇到的问题是结果永远不会有错误,所以就像@Valid 没有得到认可。我尝试将以下内容添加到我的测试类中,但它并没有解决问题。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"file:path-to/WEB-INF/spring-mvc-servlet.xml"})

有谁知道在使用 JUnit 进行测试时如何让 @Valid 被识别(和验证)?

谢谢,

马特

4

3 回答 3

6

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.

于 2012-09-06T21:31:05.567 回答
2

在调用控制器方法之前调用验证器 - 在将请求绑定到方法参数的过程中。由于在这种情况下您直接调用控制器方法,因此绕过了绑定和验证步骤。

让它工作的方法是通过 Spring MVC 堆栈调用控制器 - 有几种方法可以做到这一点,我觉得最好的方法是使用spring-test-mvc,它提供了一个很好的机制通过堆栈调用。

通过堆栈调用的另一种方法是以这种方式将 HandlerAdapter 注入到测试中:

@Autowired
private RequestMappingHandlerAdapter handlerAdapter;

然后在测试中:

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);
于 2012-09-06T21:32:03.163 回答
2

基本上你用 实例化了一个 POJO this.controller = new MyController(),然后调用它的方法this.controller.add(...)。只是带有简单对象的简单 Java,没有任何上下文:不考虑 @Valid。

@ContextConfiguration 只会加载您可能的 bean,以及可能的自定义验证器等,但它不会发挥处理 @Valid 的魔力。

您需要的是模拟对控制器add方法的请求。完全模仿它,包括验证。您离这样做不远了,因为您使用了一些 Spring 测试工具(实例化 MockHttpServletRequest)。

如果您使用 Spring 3.0.x 或更低版本,则需要执行

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

让它工作。

如果您使用 Spring 3.1+,上述解决方案将不起作用(请参阅此链接了解更多信息)!您将需要使用这个库(来自 Spring 团队,所以听起来不用担心),同时等待他们将其集成到下一个 Spring 版本中。然后你将不得不做类似的事情

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

还可以看看罗森·斯托扬切夫(Rossen Stoyanchev)的这些非常有趣的幻灯片(我们在这里讨论的部分从幻灯片 #116 开始)!

注意:我不会讨论这种测试是否被视为单元测试或集成测试。有人会说这是我们在这里进行的集成测试,因为我们模拟了请求的完整路径。但另一方面,您仍然可以使用来自 Mockito 的 @Mock 注释来模拟您的控制器(或使用任何其他模拟框架做类​​似的事情),所以其他人会说您可以将测试范围缩小到几乎纯粹的“单元测试” . 当然,您也可以使用普通的旧 Java + 模拟框架对您的控制器进行纯粹的单元测试,但在这种情况下,这将不允许您测试 @Valid 验证。做出你的选择 !:)

于 2012-09-06T21:33:17.717 回答