MockMvc vs End-to-End Tests

MockMvc 基于 spring-test 模块的 Servlet API mock 实现构建,并不依赖于正在运行的容器。因此,与使用实际客户端和实时服务器运行的完全端到端集成测试相比,存在一些差异。

MockMvc is built on Servlet API mock implementations from the spring-test module and does not rely on a running container. Therefore, there are some differences when compared to full end-to-end integration tests with an actual client and a live server running.

思考这个问题最简单的方法是从空白的 MockHttpServletRequest 开始。您添加到其中的任何内容都是请求的内容。可能会让您感到惊讶的是,默认情况下没有上下文路径;没有 jsessionid cookie;没有转发、错误或异步调度;因此,没有实际的 JSP 呈现。相反,“forwarded”和“redirected”URL 保存在 MockHttpServletResponse 中,并且可以与期望一起断言。

The easiest way to think about this is by starting with a blank MockHttpServletRequest. Whatever you add to it is what the request becomes. Things that may catch you by surprise are that there is no context path by default; no jsessionid cookie; no forwarding, error, or async dispatches; and, therefore, no actual JSP rendering. Instead, “forwarded” and “redirected” URLs are saved in the MockHttpServletResponse and can be asserted with expectations.

这意味着,如果您使用 JSP,则可以验证请求被转发的 JSP 页面,但不会呈现任何 HTML。换句话说,JSP 不会被调用。但是,请注意,所有不依赖于转发的其他呈现技术,例如 Thymeleaf 和 Freemarker,都会按预期向响应体呈现 HTML。对于通过 @ResponseBody 方法呈现 JSON、XML 和其他格式也是如此。

This means that, if you use JSPs, you can verify the JSP page to which the request was forwarded, but no HTML is rendered. In other words, the JSP is not invoked. Note, however, that all other rendering technologies that do not rely on forwarding, such as Thymeleaf and Freemarker, render HTML to the response body as expected. The same is true for rendering JSON, XML, and other formats through @ResponseBody methods.

或者,您可以考虑使用 Spring Boot 和`@SpringBootTest`提供的完全端到端集成测试支持。请参阅https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-testing[Spring Boot 参考资料指南]。

Alternatively, you may consider the full end-to-end integration testing support from Spring Boot with @SpringBootTest. See the Spring Boot Reference Guide.

每种方法都有利有弊。Spring MVC Test 中提供的选项是从经典单元测试到完全集成测试的规模上的不同停止点。可以肯定的是,Spring MVC Test 中的选项都不属于经典单元测试的范畴,但它们更接近于它。例如,您可以通过将被模拟的服务注入控制器来隔离 Web 层,在这种情况下,您仅通过 DispatcherServlet 测试 Web 层,但使用实际的 Spring 配置,就像您可以从其上层隔离地测试数据访问层一样。此外,您可以使用独立设置,一次专注于一个控制器,并手动提供使该控制器工作的所需配置。

There are pros and cons for each approach. The options provided in Spring MVC Test are different stops on the scale from classic unit testing to full integration testing. To be certain, none of the options in Spring MVC Test fall under the category of classic unit testing, but they are a little closer to it. For example, you can isolate the web layer by injecting mocked services into controllers, in which case you are testing the web layer only through the DispatcherServlet but with actual Spring configuration, as you might test the data access layer in isolation from the layers above it. Also, you can use the stand-alone setup, focusing on one controller at a time and manually providing the configuration required to make it work.

使用 Spring MVC Test 的另一个重要区别在于,在概念上,此类测试是服务器端,因此您可以检查使用了哪个处理程序、是否使用 HandlerExceptionResolver 处理了异常、模型的内容是什么、有哪些绑定错误以及其他详细信息。这意味着更容易编写期望,因为服务器不是一个不透明的盒子,就像通过实际 HTTP 客户端对其进行测试时一样。这通常是经典单元测试的优势:它更易于编写、推理和调试,但不能替代进行完全集成测试的需要。同时,重要的是不要忘记,响应是最重要的检查内容。简而言之,即使在同一个项目中,这里也有空间容纳多种测试风格和策略。

Another important distinction when using Spring MVC Test is that, conceptually, such tests are the server-side, so you can check what handler was used, if an exception was handled with a HandlerExceptionResolver, what the content of the model is, what binding errors there were, and other details. That means that it is easier to write expectations, since the server is not an opaque box, as it is when testing it through an actual HTTP client. This is generally an advantage of classic unit testing: It is easier to write, reason about, and debug but does not replace the need for full integration tests. At the same time, it is important not to lose sight of the fact that the response is the most important thing to check. In short, there is room here for multiple styles and strategies of testing even within the same project.