领先一步
VMware 提供培训和认证,助您加速进步。
了解更多[callout title=更新 2012年12月19日] 最终版 Spring Framework 参考文档包含迁移指南以及关于 Spring MVC Test 的完整章节。[/callout]
上周 Juergen Hoeller 宣布发布 Spring Framework 3.2 RC1,而 Sam Brannen 讨论了其 spring-test 模块中令人兴奋的新增功能,例如对 WebApplicationContext
的支持以及加载上下文层次结构的未来计划。今天我将继续这个话题,介绍另一个令人兴奋的 spring-test
新增功能。在 3.2 RC1 中,我们为测试 Spring MVC 应用添加了一流的支持,包括客户端和服务器端。
这里讨论的 Spring MVC Test 框架源自 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"));
}
}
[callout title=静态导入]流畅的 API 依赖于这些静态导入:MockMvcBuilders.*
MockMvcRequestBuilders.*
MockMvcResultMatchers.*
为了代码补全的便利,请将它们添加到 Eclipse 首选项的“favorite types”中,或者只需记住以 MockMvc*
开头的类。[/callout]
如你所见,我们使用新的 @WebAppConfiguration
注解来加载我们的 Spring MVC 配置。然后我们将生成的 WebApplicationContext
注入到测试类的字段中,并用它创建一个 MockMvc
,后者反过来用于执行请求和定义期望。
[callout title=上下文缓存]TestContext 框架会在整个测试套件甚至整个 JVM 中缓存已加载的 Spring 配置。因此,测试的速度应该非常理想。[/callout]
正如现有的控制器单元测试一样,Spring MVC Test 基于 spring-test
中的 mock 请求和响应构建,并且不需要运行中的 servlet 容器。主要区别在于实际的 Spring MVC 配置是通过 TestContext 框架加载的,并且请求是通过实际调用 DispatcherServlet
和所有与运行时相同的 Spring MVC 基础设施来执行的。
与现有的控制器单元测试类似,你也可以考虑向控制器注入 mock 服务,以便专注于测试 Web 层,并避免例如访问数据库。因此,你可以加载创建 mock 对象的配置,而不是加载实际的业务和持久化服务。例如
@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
,你可能想对其进行测试,你可以针对正在运行的服务器进行测试,或者 mock RestTemplate。客户端 REST 测试支持提供了第三种选择,即使用实际的 RestTemplate
,但通过自定义的 ClientHttpRequestFactory
进行配置,该工厂会对照实际请求检查期望,并返回 stub 响应。
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 首选项的“favorite types”中,或者只需记住以 "MockRest*"
开头的类。[/callout]
如你所见,我们创建一个 RestTemplate
实例并将其传递给 MockRestServiceServer
进行配置。然后我们定义期望请求的特征,并提供要返回的 stub 响应。我们可以定义任意数量的期望请求和 stub 响应。在测试结束时,我们可以使用 verify
方法检查所有期望的请求是否都已执行。
还有很多可以讨论的内容,但最好还是在你自己的项目中使用 Spring Framework 3.2 RC1 或 Github 上的独立项目(适用于 Spring Framework 3.1)进行尝试。
最近我亲自尝试了一下,为 spring-mvc-showcase 的所有控制器方法添加了一套全面的测试。这是一次有益的实践,因为它帮我发现了一个 bug。因此,我希望它也能对其他人有所帮助。
如果你需要更多示例,Spring Framework 中还有许多演示测试,包括异步请求测试、过滤器测试、JSON 响应、XML 响应,以及许多其他示例。