领先一步
VMware 提供培训和认证,以加速您的进步。
了解更多[提示框 标题=2012年12月19日更新]最终的 Spring Framework 参考文档包含了迁移指南以及关于 Spring MVC 测试的完整章节。[/提示框]
上周Juergen Hoeller 宣布发布 Spring Framework 3.2 RC1,Sam Brannen 讨论了其spring-test 模块中令人兴奋的补充,例如对 `WebApplicationContext` 的支持以及即将推出的加载上下文层次结构的计划。今天,我将继续讨论这个主题,并介绍另一个令人兴奋的 `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 来指定要执行的请求和您期望的响应,那又将会怎样呢?所有这些都不需要 servlet 容器。这就是 Spring MVC Test 的作用。这是一个例子
@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"));
}
}
[提示框 标题=静态导入]流畅的 API 依赖于这些静态导入:`MockMvcBuilders.*` `MockMvcRequestBuilders.*` `MockMvcResultMatchers.*` 为了获得代码完成辅助功能,请在 Eclipse 首选项中将它们添加为“收藏类型”,或者只需记住以 `MockMvc*` 开头的类即可。[/提示框]
如您所见,我们使用新的 `@WebAppConfiguration` 注解来加载我们的 Spring MVC 配置。然后,我们将生成的 `WebApplicationContext` 注入到测试类的字段中,并用它来创建一个 `MockMvc`,然后使用它来执行请求和定义期望。
[提示框 标题=上下文缓存]TestContext 框架在测试套件中甚至在 JVM 中缓存加载的 Spring 配置。因此,测试速度应该非常理想。[/提示框]
与现有的控制器单元测试一样,Spring MVC Test 基于 `spring-test` 中的模拟请求和响应,不需要运行 servlet 容器。主要区别在于实际的 Spring MVC 配置是通过 TestContext 框架加载的,并且请求是通过实际调用 `DispatcherServlet` 和运行时使用的所有相同的 Spring MVC 基础架构来执行的。
同样类似于现有的控制器单元测试,您可以考虑使用模拟服务注入控制器,以便专注于测试 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();
[提示框 标题=静态导入]流畅的 API 需要静态导入
`MockRestRequestMatchers.*` `MockRestResponseCreators.*` 为了获得代码完成辅助功能,请在 Eclipse 首选项中将它们添加为“收藏类型”,或者只需记住以 `"MockRest*"` 开头的类即可。[/提示框]
如您所见,我们创建了一个 `RestTemplate` 实例,并将其传递给 `MockRestServiceServer` 进行配置。然后,我们定义预期请求的特性,并提供要返回的存根响应。我们可以定义任意数量的预期请求和存根响应。在测试结束时,我们可以使用 `verify` 方法来检查是否执行了所有预期请求。
我们还可以讨论更多内容,但最好您自己尝试一下,使用 Spring Framework 3.2 RC1 或 Github 上的独立项目(适用于 Spring Framework 3.1)。
我最近自己也进行了这项练习,为 spring-mvc-showcase 的所有控制器方法添加了一套全面的测试。这是一个有用的练习,因为它帮助我发现了一个错误。因此,我相信它对其他人也有用。
如果您想要更多示例,包括Spring Framework 中还有许多演示测试 带有异步请求的测试、带有过滤器的测试、JSON 响应、XML 响应等等。