领先一步
VMware 提供培训和认证,助您加速进步。
了解更多[callout title=2012年12月19日更新] Spring Framework 最终参考文档包含迁移指南以及一个完整的章节介绍 Spring MVC 测试。 [/callout]
上周,Juergen Hoeller 发布了 Spring Framework 3.2 RC1,Sam Brannen 讨论了其 `spring-test` 模块中的激动人心的新增功能,例如对 `WebApplicationContext` 的支持以及加载上下文层次结构(hierarchy of contexts)的后续计划。今天,我将继续这个话题,介绍 `spring-test` 的另一个激动人心的新增功能。在 3.2 RC1 中,我们为 Spring MVC 应用程序的客户端和服务器端测试增加了第一类支持。
本文讨论的 Spring MVC 测试框架源自 Github 上的一个独立项目,在该项目中,功能经过一年多的发展,并获得了许多用户的持续反馈。感谢所有早期采用者、所有贡献者、报告问题者、评论者,以及所有撰写博客或发表相关演讲的人。
从 Spring 3.2 RC1 开始,来自独立项目的代码已添加到 Spring Framework 中,并可在 `spring-test` 模块中使用,但包名称稍有修改,并增加了对 3.2 特有功能(如异步请求等)的支持。独立项目将继续为针对 Spring MVC 3.1 的应用程序测试而存在。
话不多说,让我们更深入、更详细地了解一下。
今天,您是如何测试 Spring MVC 控制器的?最可能的方式是通过简单的单元测试,可能涉及到 `MockHttpServletRequest` 和 `-Response`。这样做很简单,但测试不够充分。控制器有注解来表达它们的映射方式、需要提取、转换和验证哪些请求数据、是否写入响应正文、如何处理异常等等。所有这些由框架根据这些注解执行的操作,如果只编写简单的单元测试,都将保持未被测试状态。
如果能够重写这些控制器单元测试,但不再直接调用控制器,而是通过 DispatcherServlet 调用,就像在运行时一样呢?如果能够使用流畅的 API(fluent API)来指定要执行的请求和预期的响应呢?所有这些都无需 Servlet 容器。这就是 Spring MVC 测试所做的。这里有一个例子:
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration("servlet-context.xml")
public class SampleTests {
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@Before
public void setup() {
this.mockMvc = webAppContextSetup(this.wac).build();
}
@Test
public void getFoo() throws Exception {
this.mockMvc.perform(get("/foo").accept("application/json"))
.andExpect(status().isOk())
.andExpect(content().mimeType("application/json"))
.andExpect(jsonPath("$.name").value("Lee"));
}
}
[callout title=静态导入]流畅 API 依赖于这些静态导入:`MockMvcBuilders.*` `MockMvcRequestBuilders.*` `MockMvcResultMatchers.*` 为了代码补全的帮助,请在 Eclipse 的首选项中将它们添加为“收藏夹类型”,或者简单地记住以 `MockMvc*` 开头的类。[/callout]
正如您所见,我们使用新的 `@WebAppConfiguration` 注解来加载我们的 Spring MVC 配置。然后,我们将生成的 `WebApplicationContext` 注入到测试类字段中,并使用它来创建一个 `MockMvc`,然后用它来执行请求和定义期望。
[callout title=上下文缓存]TestContext 框架会在整个测试套件甚至整个 JVM 中缓存已加载的 Spring 配置。因此,测试速度应该非常理想。[/callout]
与现有的控制器单元测试一样,Spring MVC 测试建立在 `spring-test` 的模拟请求和响应之上,并且不需要正在运行的 Servlet 容器。主要区别在于,实际的 Spring MVC 配置通过 TestContext 框架加载,并且通过实际调用 `DispatcherServlet` 和运行时使用的所有相同的 Spring MVC 基础架构来执行请求。
与现有的控制器单元测试类似,您也可以考虑用模拟服务(mock services)注入控制器,以便专注于测试 Web 层,避免访问数据库等。因此,您不必加载实际的业务和服务,而是可以加载创建模拟的配置。例如:
@Configuration
public class MyConfig {
@Bean
public FooService fooService() {
return Mockito.mock(FooService.class);
}
}
或者在 XML 配置中
<bean class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="org.example.FooService"/>
</bean>
鉴于我们不在实际的 Servlet 容器中运行,究竟有多少会奏效,有多少不会奏效?在大多数情况下,一切都会像在运行时一样奏效。您甚至可以注册 Servlet 过滤器,启用 Spring Security 等功能。大多数渲染技术,如 JSON/XML、Freemarker、Velocity、Thymeleaf、Excel、PDF 等,都会奏效。唯一被排除在外的渲染技术是 JSP,因为那需要 Servlet 容器。对于 JSP,您仍然可以验证请求转发到的 JSP、模型中有什么属性、是否抛出了任何异常等等。
客户端 REST 测试的理念是什么?如果您有使用 `RestTemplate` 的代码,您可能想测试它,为此您可以将目标定位到一个正在运行的服务器或模拟 RestTemplate。客户端 REST 测试支持提供了第三种选择,即使用实际的 `RestTemplate`,但使用自定义的 `ClientHttpRequestFactory` 来配置它,该工厂会检查实际请求的期望并返回存根响应。
RestTemplate restTemplate = new RestTemplate();
MockRestServiceServer mockServer = MockRestServiceServer.createServer(restTemplate);
mockServer.expect(requestTo("/greeting"))
.andRespond(withSuccess("Hello world", "text/plain"));
// use RestTemplate ...
mockServer.verify();
[callout title=静态导入]流畅 API 需要静态导入
`MockRestRequestMatchers.*` `MockRestResponseCreators.*` 为了代码补全的帮助,请在 Eclipse 的首选项中将它们添加为“收藏夹类型”,或者简单地记住以 `"MockRest*"` 开头的类。[/callout]
正如您所见,我们创建了一个 `RestTemplate` 实例并将其传递给 `MockRestServiceServer` 进行配置。然后,我们定义了预期请求的特征,并提供了一个存根响应以供返回。我们可以定义任意数量的预期请求和存根响应。在测试结束时,我们可以使用 `verify` 方法来检查所有预期的请求是否都已执行。
还有很多我们可以讨论的内容,但最好还是您亲自尝试一下,在您的项目中使用 Spring Framework 3.2 RC1 或 Github 上的独立项目,后者适用于 Spring Framework 3.1。
最近我亲身体验了添加一套全面的测试来覆盖 spring-mvc-showcase 的所有控制器方法。这是一次有用的练习,因为它帮助我发现了一个 bug。因此,我期望它对其他人也很有用。
Spring Framework 中还有许多演示测试,如果您需要更多示例,包括异步请求测试、过滤器测试、JSON 响应、XML 响应等等。