Spring MVC Test Framework简译
来源:互联网 发布:好看的动漫 知乎 编辑:程序博客网 时间:2024/06/16 15:49
10.3.6 Spring MVC Test Framework
Spring MVC 测试框架(Spring MVC Test framework)支持junit的,用于测试客户端/(Spring MVC编码)服务器端的API。它通过TestContext框架加载spring配置文件,DispatcherServlet处理请求。它基本上接近于全量集成测试,但是不需要启动Servlet容器。
客户端测试需要基于RestTemplate,并依赖于RestTemplate编写测试用例,不需要启动一个server来响应请求。
Spring MVC测试框架原理是在DispatcherServlet调用controller时,重写controller,用于执行请求和生成响应。此时仍可以mock Controller 依赖的对象,并注入到Controller中。因此测试仍可以只关注web层。
Spring MVC测试基于Servlet API的mock实现,这样处理请求和生成返回信息就不用启动Servlet容器了。除了渲染JSP 页面外,其它功能都可以在Spring MVC框架中被测试。如果你知道MockHttpServletResponse是如何工作的,你就会知道forwards和redirects并没有指正执行,而是url地址被保存下来,然后可以在测试代码中验证。换句话说,如果你使用JSP文件,你可以验证一个请求的forward到哪个jsp页面。
也可以说,所有包含 @ResponseBody 和返回View类型(Freemarker, Velocity, Thymeleaf,jsp)的用于产生HTML、JSON、XML内容的方法,都可以在Spring MVC测试框架下如预期一样工作,并在response中包含生成的内容。
下面的测试用例需要一个JSON格式的账户信息:
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;@RunWith(SpringJUnit4ClassRunner.class)@WebAppConfiguration@ContextConfiguration("test-servlet-context.xml")public class ExampleTests { @Autowired private WebApplicationContext wac; private MockMvc mockMvc; @Before public void setup() { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); } @Test public void getAccount() throws Exception { this.mockMvc.perform(get("/accounts/1") .accept(MediaType.parseMediaType("application/json;charset=UTF-8"))) .andExpect(status().isOk()) .andExpect(content().contentType("application/json")) .andExpect(jsonPath("$.name").value("Lee")); }}
这个测试用例依赖TestContext框架提供的WebApplicationContext类。该用例通过xml文件加载Spring配置,并将WebApplicationContext实例注入到test中,并用WebApplicationContext创建了MockMvc实例。
然后使用MockMvc执行了一个“/accounts/1”的请求,并验证response的状态是否是200,内容类型是否是”application/json”,并且返回的JSON数据中是否包含“name”属性,且其值是“Lee”。Json数据的解析参见Jayway的JsonPath project。还有很多类似的用于验证返回值的方法,这些方法会在后面讨论。
Static Imports
上面的列子中使用静态导入,比如:MockMvcRequestBuilders.*, MockMvcResultMatchers.*, and MockMvcBuilders.*. 一个简单的找到这些类的方法是搜索以“MockMvc”开头的类。如果使用Eclipse,你可以将这些类加入到“favorite static members”中:Java → Editor → Content Assist → Favorites。这样就可以在输入静态方法的首字符时,Eclipse会弹出代码补全提示。其它IDE(比如IntelliJ)可能不需要任何配置,是否需要配置需要你检查你的IDE是否支持静态对象的代码补全功能。
Setup Options
服务端测试setup的目的是创建MockMvc实例,以用于执行请求。有两种方式用来创建MockMvc实例。
第一中方式是加载spring配置,并在测试代码中注入WebApplicationContext类,然后用WebApplicationContext创建一个MockMvc对象:
@RunWith(SpringJUnit4ClassRunner.class)@WebAppConfiguration@ContextConfiguration("my-servlet-context.xml")public class MyWebTests { @Autowired private WebApplicationContext wac; private MockMvc mockMvc; @Before public void setup() { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); } // ...}
第二个选项是不加载spring配置,只简单的注册一个controller实例。
public class MyWebTests { private MockMvc mockMvc; @Before public void setup() { this.mockMvc = MockMvcBuilders.standaloneSetup(new AccountController()).build(); } // ...}
你将会用那种方式呢?
使用“webAppContextSetup”方式会加载spring mvc的配置文件,这样会有一个更加齐全的集成测试环境。因为TestContext框架缓存了spring配置,所以可以多个测试方法执行的更快。此外,你还可以通过spring配置文件mock一些service类,这样就可以让测试只关注web层。下面是通过Mockitomock了一个service类:
<bean id="accountService" class="org.mockito.Mockito" factory-method="mock"> <constructor-arg value="org.example.AccountService"/></bean>
然后你就可以将mock service注入到test类中,然后验证期望:
@RunWith(SpringJUnit4ClassRunner.class)@WebAppConfiguration@ContextConfiguration("test-servlet-context.xml")public class AccountTests { @Autowired private WebApplicationContext wac; private MockMvc mockMvc; @Autowired private AccountService accountService; // ...}
“standaloneSetup”的方式,从某方面来说更接近单元测试。它一次只测试一个controller,这个controller可以手工注入mock依赖,并不需要加载spring配置文件。这样的测试更容易看出在测试哪个controller,是否需要加载特殊的Spring MVC配置,等等。这种方式也更容易写ad-hoc测试来验证某些行为或测试一个问题。
正像集成测试vs单元测试,并没有谁对谁错的答案。使用“standaloneSetup”方式时需要额外使用“webAppContextSetup”方式来验证Spring MVC的配置。当然,你可以所有的测试用例都用“webAppContextSetup”的方式来写。
Performing Requests
为了执行一个请求,你需要指定使用合适的HTTP method,并指定MockHttpServletRequest请求内容的格式:
比如:
mockMvc.perform(post("/hotels/{id}", 42).accept(MediaType.APPLICATION_JSON));
另外,对所有的http method,你都可以执行一个上传文件请求(它会在内部创建一个MockMultipartHttpServletRequest实例):
mockMvc.perform(fileUpload("/doc").file("a1", "ABC".getBytes("UTF-8")));
查询参数可以使用URI模块定义:
mockMvc.perform(get("/hotels?foo={foo}", "bar"));
或者增加增加请求参数:
mockMvc.perform(get("/hotels").param("foo", "bar"));
如果应用程序代码需要请求时携带参数,多数情况下,并不会检查请求参数字符串,此时可以随意增加参数。请记住如果使用URI模板携带请求参数则参数需要encode,使用param(…)方法参数不需要encode。
大部分情况下并不需要使用context path 和 Servlet path,但是如果你需要测试请求的全路径,你需要设置contextPath和servletPath:
mockMvc.perform(get("/app/main/hotels/{id}").contextPath("/app").servletPath("/main"))
看上面的例子,为每一个request设置contextPath和servletPath设置值是很笨重的,你可以在构建MockMvc对象时设置默认的request属性:
public class MyWebTests { private MockMvc mockMvc; @Before public void setup() { mockMvc = standaloneSetup(new AccountController()) .defaultRequest(get("/") .contextPath("/app").servletPath("/main") .accept(MediaType.APPLICATION_JSON).build(); }
上面的request属性会应用在所有MockMvc的请求中。如果在某个请求中设置了某属性,则会覆盖MockMvc中相应属性的默认值。这也是为什么可以设置HTTP 方法 和 URI 的原因,因为每一个request都必须设置这两个值。
Defining Expectations
期望:你可以在perform之后使用.andExpect(..) 定义一个或多个期望。
mockMvc.perform(get("/accounts/1")).andExpect(status().isOk());
在MockMvcResultMatchers.* 中定义了大量静态的方法,有些方法的返回类型可用于断言请求的结果。断言一般情况下分为两类。
一类断言用来验证response的属性,比如status,headers,content。这些正是测试最重要的内容。
第二类断言用来检查Spring MVC 具体结构,比如哪个controller方法处理的request,是否有异常抛出并被处理,model的内容是什么,选择了哪个view,增加了哪些临时属性,等等。也可以验证Servlet的具体结构,比如request和session的属性 。下面是测试 绑定/验证 失败的断言:
mockMvc.perform(post("/persons")) .andExpect(status().isOk()) .andExpect(model().attributeHasErrors("person"));
大部分情况下,dump请求的结果是很有用的。可以像下面这样使用MockMvcResultHandlers 的print():
mockMvc.perform(post("/persons")) .andDo(print()) .andExpect(status().isOk()) .andExpect(model().attributeHasErrors("person"));
一旦请求产生了未捕获的异常,print()方法会向System.out 中打印所有可能的结果。
在某些情况下,你可能想直接获取请求结果,尤其在使用期望无法验证的时候,你可以在所有的期望后增加.andReturn()方法:
MvcResult mvcResult = mockMvc.perform(post("/persons")) .andExpect(status().isOk()).andReturn();// ...
当所有的测试都有相同的期望时,你可以在构建MockMvc时定义通用的期望:
standaloneSetup(new SimpleController()) .alwaysExpect(status().isOk()) .alwaysExpect(content().contentType("application/json;charset=UTF-8")) .build()
注意:期望每次都会被应用并且不能被覆盖,除非使用不同的MockMvc实例。
当响应的JSON文本中包含了Spring HATEOAS形式的 超媒体链接时,这个链接可以这样验证:
mockMvc.perform(get("/people").accept(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$.links[?(@.rel == self)].href") .value("http://localhost:8080/people"));
当响应的XML文本中包含了Spring HATEOAS形式的 超媒体链接时,这个链接可以这样验证:
Map<String, String> ns = Collections.singletonMap("ns", "http://www.w3.org/2005/Atom");mockMvc.perform(get("/handle").accept(MediaType.APPLICATION_XML)) .andExpect(xpath("/person/ns:link[@rel=self]/@href", ns) .string("http://localhost:8080/people"));
Filter Registrations
在生成MockMvc对象时,你可以注册一个或多个Filter:
mockMvc = standaloneSetup(new PersonController()) .addFilters(new CharacterEncodingFilter()).build();
注册的过滤器会被spring-test框架的MockFilterChain调用,最后一个filter会委托给DispatcherServlet.
Further Server-Side Test Examples
该框架自己的测试用例中包含很多简单的测试用例,用来说明怎样使用Spring MVC Test。也可以查看spring-mvc-showcase项目,这里有使用Spring MVC Test的完整的覆盖测试。
Client-Side REST Tests
客户端测试需要使用RestTemplate:其定义了对request的期望并提供了一个“预存”的响应:
RestTemplate restTemplate = new RestTemplate();MockRestServiceServer mockServer = MockRestServiceServer.createServer(restTemplate);mockServer.expect(requestTo("/greeting")).andRespond(withSuccess("Hello world", "text/plain"));// use RestTemplate ...mockServer.verify();
在上例中,MockRestServiceServer是通过ClientHttpRequestFactory配置RestTemplate,通过期望断言请求,并返回“预存”的响应。在这个例子中,我们期望一个”/greeting”请求,并获得一个状态200的”text/plain”类型的响应。我们根据需要定义任意多个请求和预存的响应。
一旦定义了requests和responses,RestTemplate就可以在客户端测试代码中使用了。测试例子中的最后
一行代码“mockServer.verify()”,能验证是否执行了期望的requests。
Static Imports
和服务端一样,客户单测试可需要一些静态导入。可以搜索”MockRest*”找打这些需要静态导入的类。Eclipse使用者可以将”MockRestRequestMatchers.” and “MockRestResponseCreators.“增加到“favorite static members”中:Java → Editor → Content Assist → Favorites。这样就可以在输入静态方法的首字符时,Eclipse会弹出代码补全提示。其它IDE(比如IntelliJ)可能不需要任何配置,是否需要配置需要你检查你的IDE是否支持静态对象的代码补全功能。
Further Examples of Client-side REST Tests
Spring MVC测试框架里面的测试用例,包含了对客户端REST测试的例子。
附加两个具体的测试用例
例子1,加载spring 配置文件的集成测试:
/** * 集成式的测试 * * @author jeff * */@RunWith(SpringJUnit4ClassRunner.class)@WebAppConfiguration(value = "src/main/webapp")@ContextConfiguration(locations = { "classpath:/spring/spring-test-web.xml" })public class OperationDriverControllerTest { @Autowired protected WebApplicationContext wac; protected MockMvc mockMvc; @Before public void setUp() { mockMvc = MockMvcBuilders.webAppContextSetup(wac).build(); } /** * 区长管理首页测试 * * @throws Exception */ @Test public void platoonManageDriverTest() throws Exception { MvcResult result = mockMvc.perform(MockMvcRequestBuilders .post("/operationDriver/platoonManageDriverPage")) .andExpect(MockMvcResultMatchers.view() .name("platoonManageDriver/index")) .andDo(MockMvcResultHandlers.print()).andReturn(); Assert.assertNotNull(result.getModelAndView().getViewName()); } /** * 区长管理工具查询接口测试 * * @throws Exception */ @Test public void exportPlatoonManageDriverTest() throws Exception { long dt = 1507651200L; Integer cityId = 1; int pageNo = 1; int pageSize = 10; MvcResult result = mockMvc .perform(MockMvcRequestBuilders.post( "/operationDriver/platoonManageDriver?dt=${dt} &cityId=${cityId} &pageNo=${pageNo}&pageSize=${pageSize}", dt, cityId, pageNo, pageSize)) .andExpect(MockMvcResultMatchers.status().isOk()) .andDo(MockMvcResultHandlers.print()).andReturn(); Assert.assertNotNull(result.getResponse().getContentAsString()); }}
例子2,standaloneSetup+mockito 测试单个controller:
/** * 单个controller mock测试 * * @author jeff * */public class OperationDriverControllerTest2 { private MockMvc mockMvc; //要测试的controller private OperationDriverController operationDriverController = new OperationDriverController(); /** * controller的依赖,此处通过接口mock */ @Mock private DriverOperationService driverOperationService; private long dt = 1507651200L; private Integer cityId = 1; private int pageNo = 1; private int pageSize = 10; private Integer warehouseId = null; private Long stationRegionId = null; private String driverName = null; private Long driverAuthId = null; private String squadName = null; private String platoonName = null; Page<PlatoonManageDriverBean> platoonManageDriverQuery = new Page<PlatoonManageDriverBean>(pageSize, pageNo); private ServiceResult<Page<PlatoonManageDriverBean>> queryResult = ServiceResultUtil.success(platoonManageDriverQuery); @Before public void setup() { MockitoAnnotations.initMocks(this); /** * stub mock对象的response */ Mockito.when(driverOperationService.platoonManageDriver (DateUtil.formatYMD(DateUtil.fromUnixtime(dt)), cityId, warehouseId, stationRegionId, driverName, driverAuthId, squadName, platoonName, pageNo, pageSize)).thenReturn(queryResult); //向controller中注入mock对象 ReflectionTestUtils.setField(operationDriverController, "driverOperationService", driverOperationService); this.mockMvc =MockMvcBuilders.standaloneSetup(operationDriverController) .build(); } /** * 区长管理首页测试 * * @throws Exception */ @Test public void platoonManageDriverTest() throws Exception { MvcResult result = mockMvc.perform(MockMvcRequestBuilders .post("/operationDriver/platoonManageDriverPage")) .andExpect(MockMvcResultMatchers.view() .name("platoonManageDriver/index")) .andDo(MockMvcResultHandlers.print()).andReturn(); Assert.assertNotNull(result.getModelAndView().getViewName()); } /** * 区长管理工具查询接口测试 * * @throws Exception */ @Test public void exportPlatoonManageDriverTest() throws Exception { MvcResult result = mockMvc .perform(MockMvcRequestBuilders. post( "/operationDriver/platoonManageDriver?dt=${dt} &cityId=${cityId}&pageNo=${pageNo}&pageSize=${pageSize}", dt, cityId, pageNo, pageSize)) .andExpect(MockMvcResultMatchers.status().isOk()) .andDo(MockMvcResultHandlers.print()).andReturn(); Assert.assertNotNull(result.getResponse().getContentAsString()); }}
- Spring MVC Test Framework简译
- Spring MVC test
- spring-mvc-test(单元测试)
- Spring Framework MVC
- Spring MVC Framework Tutorial
- Spring - MVC Framework 教程
- spring mvc test by mock
- Spring MVC framework总体分析
- Spring MVC framework 执行过程
- Spring MVC framework深入分析
- Spring Framework MVC 学习感想!
- Spring MVC framework总体分析
- Spring MVC framework 执行过程
- Spring MVC framework深入分析
- SPRING 3 MVC FRAMEWORK INTRODUCTION
- Spring MVC framework[1] Configuration
- Spring MVC framework[2] Controller
- Spring 3 MVC – Introduction to Spring 3 MVC Framework
- ResourcePathLocationType Target runtime com.genuitec.runtime.generic.jee60 is not def
- Redis总结
- uboot中ethernet网口实现分析
- Android Design Support Library使用
- 数据库——sql中的主外键约束及其他常用约束
- Spring MVC Test Framework简译
- bzoj3444(搜索)
- Andrew NG 机器学习 练习6-Support Vector Machines
- Android 二级列表购物车
- 2年Java开发工作经验面试总结
- 初识Nginx
- 搭建FastDFS,Nginx,fastdfs-nginx-module图片资源服务器
- 实验二 链表的基本操作
- 搬走了