上一篇《Spring MVC测试框架详解——服务端测试》已经介绍了服务端测试,接下来再看看如果测试Rest客户端,对于客户端测试以前经常使用的方法是启动一个内嵌的jetty/tomcat容器,然后发送真实的请求到相应的控制器;这种方式的缺点就是速度慢;自Spring 3.2开始提供了对RestTemplate的模拟服务器测试方式,也就是说使用RestTemplate测试时无须启动服务器,而是模拟一个服务器进行测试,这样的话速度是非常快的。
2 RestTemplate客户端测试
整个环境在上一篇《Spring MVC测试框架详解——服务端测试》基础上进行构建。
UserRestController控制器
@RestController
@RequestMapping("/users")
public class UserRestController {
private UserService userService;
@Autowired
public UserRestController(UserService userService) {
this.userService = userService;
}
@RequestMapping(value = "/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public User findById(@PathVariable("id") Long id) {
return userService.findById(1L);
}
@RequestMapping(method = RequestMethod.POST)
public ResponseEntity<User> save(@RequestBody User user, UriComponentsBuilder uriComponentsBuilder) {
//save user
user.setId(1L);
MultiValueMap headers = new HttpHeaders();
headers.set("Location", uriComponentsBuilder.path("/users/{id}").buildAndExpand(user.getId()).toUriString());
return new ResponseEntity(user, headers, HttpStatus.CREATED);
}
@RequestMapping(value = "/{id}", method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON_VALUE)
@ResponseStatus(HttpStatus.NO_CONTENT)
public void update(@RequestBody User user) {
//update by id
}
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
public void delete(@PathVariable("id") Long id) {
//delete by id
}
}
2.1 使用内嵌Jetty方式启动容器进行
需要添加jetty依赖:
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jetty.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>
<version>${jetty.version}</version>
<scope>test</scope>
</dependency>
如果要测试JSP,请添加
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-jsp</artifactId>
<version>${jetty.version}</version>
<scope>test</scope>
</dependency>
版本:<jetty.version>8.1.8.v20121106</jetty.version>
测试示例(EmbeddedJettyClientTest.java)
public class EmbeddedJettyClientTest extends AbstractClientTest {
private static Server server;
@BeforeClass
public static void beforeClass() throws Exception {
//创建一个server
server = new Server(8080);
WebAppContext context = new WebAppContext();
String webapp = "spring-mvc-test/src/main/webapp";
context.setDescriptor(webapp + "/WEB-INF/web.xml"); //指定web.xml配置文件
context.setResourceBase(webapp); //指定webapp目录
context.setContextPath("/");
context.setParentLoaderPriority(true);
server.setHandler(context);
server.start();
}
@AfterClass
public static void afterClass() throws Exception {
server.stop(); //当测试结束时停止服务器
}
@Test
public void testFindById() throws Exception {
ResponseEntity<User> entity = restTemplate.getForEntity(baseUri + "/{id}", User.class, 1L);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertThat(entity.getHeaders().getContentType().toString(), containsString(MediaType.APPLICATION_JSON_VALUE));
assertThat(entity.getBody(), hasProperty("name", is("zhang")));
}
//省略其他,请参考github
}
此处通过内嵌Jetty启动一个web容器,然后使用RestTemplate访问真实的uri进行访问,然后进行断言验证。
这种方式的最大的缺点是如果我只测试UserRestController,其他的组件也会加载,属于集成测试,速度非常慢。伴随着Spring Boot项目的发布,我们可以使用Spring Boot进行测试。
2.2 使用Spring Boot进行测试
spring boot请参考spring boot官网 和《Spring Boot——2分钟构建spring web mvc REST风格HelloWorld》进行入门。通过spring boot我们可以只加载某个控制器进行测试。更加方便。
添加spring-boot-starter-web依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring.boot.version}</version>
<scope>test</scope>
</dependency>
版本:<spring.boot.version>0.5.0.BUILD-SNAPSHOT</spring.boot.version>,目前还处于SNAPSHOT版本。
测试示例(SpringBootClientTest.java)
public class SpringBootClientTest extends AbstractClientTest {
private static ApplicationContext ctx;
@BeforeClass
public static void beforeClass() throws Exception {
ctx = SpringApplication.run(Config.class); //启动服务器 加载Config指定的组件
}
@AfterClass
public static void afterClass() throws Exception {
SpringApplication.exit(ctx);//退出服务器
}
@Test
public void testFindById() throws Exception {
ResponseEntity<User> entity = restTemplate.getForEntity(baseUri + "/{id}", User.class, 1L);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertThat(entity.getHeaders().getContentType().toString(), containsString(MediaType.APPLICATION_JSON_VALUE));
assertThat(entity.getBody(), hasProperty("name", is("zhang")));
}
//省略其他,请参考github
@Configuration
@EnableAutoConfiguration
static class Config {
@Bean
public EmbeddedServletContainerFactory servletContainer() {
return new JettyEmbeddedServletContainerFactory();
}
@Bean
public UserRestController userController() {
return new UserRestController(userService());
}
@Bean
public UserService userService() {
//Mockito请参考 http://stamen.iteye.com/blog/1470066
UserService userService = Mockito.mock(UserService.class);
User user = new User();
user.setId(1L);
user.setName("zhang");
Mockito.when(userService.findById(Mockito.any(Long.class))).thenReturn(user);
return userService;
// return new UserServiceImpl(); //此处也可以返回真实的UserService实现
}
}
}
通过SpringApplication.run启动一个服务器,然后Config.xml是Spring的Java配置方式,此处只加载了UserRestController及其依赖UserService,对于UserService可以通过如Mockito进行模拟/也可以注入真实的实现,Mockito请参考《单元测试系列之2:模拟利器Mockito》。可以通过EmbeddedServletContainerFactory子类指定使用哪个内嵌的web容器(目前支持:jetty/tomcat)。
public class MockServerClientTest extends AbstractClientTest {
private MockRestServiceServer mockServer;
@Before
public void setUp() throws Exception {
super.setUp();
//模拟一个服务器
mockServer = createServer(restTemplate);
}
@Test
public void testFindById() throws JsonProcessingException {
String uri = baseUri + "/{id}";
Long id = 1L;
User user = new User();
user.setId(1L);
user.setName("zhang");
String userJson = objectMapper.writeValueAsString(user);
String requestUri = UriComponentsBuilder.fromUriString(uri).buildAndExpand(id).toUriString();
//添加服务器端断言
mockServer
.expect(requestTo(requestUri))
.andExpect(method(HttpMethod.GET))
.andRespond(withSuccess(userJson, MediaType.APPLICATION_JSON));
//2、访问URI(与API交互)
ResponseEntity<User> entity = restTemplate.getForEntity(uri, User.class, id);
//3.1、客户端验证
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertThat(entity.getHeaders().getContentType().toString(), containsString(MediaType.APPLICATION_JSON_VALUE));
assertThat(entity.getBody(), hasProperty("name", is("zhang")));
//3.2、服务器端验证(验证之前添加的服务器端断言)
mockServer.verify();
}
//省略其他,请参考github
}
测试步骤:
1、准备测试环境
首先创建RestTemplate,然后通过MockRestServiceServer.createServer(restTemplate)创建一个Mock Server,其会自动设置restTemplate的requestFactory为RequestMatcherClientHttpRequestFactory(restTemplate发送请求时都通过ClientHttpRequestFactory创建ClientHttpRequest)。
2、调用API
即restTemplate.getForEntity(uri, User.class, id)访问rest web service;
3、断言验证
3.1、客户端请求断言验证
如mockServer.expect(requestTo(requestUri)).andExpect(method(HttpMethod.GET)):即会验证之后通过restTemplate发送请求的uri是requestUri,且请求方法是GET;
3.2、服务端响应断言验证
首先通过mockServer.andRespond(withSuccess(new ObjectMapper().writeValueAsString(user), MediaType.APPLICATION_JSON));返回给客户端响应信息;
然后restTemplate就可以得到ResponseEntity,之后就可以通过断言进行验证了;
4、 卸载测试环境
对于单元测试步骤请参考:加速Java应用开发速度3——单元/集成测试+CI。
2.4 了解测试API
MockRestServiceServer
用来创建模拟服务器,其提供了createServer(RestTemplate restTemplate),传入一个restTemplate即可创建一个MockRestServiceServer;在createServer中:
MockRestServiceServer mockServer = new MockRestServiceServer();
RequestMatcherClientHttpRequestFactory factory = mockServer.new RequestMatcherClientHttpRequestFactory();
restTemplate.setRequestFactory(factory);
即模拟一个ClientHttpRequestFactory,然后设置回RestTemplate,这样所有发送的请求都会到这个MockRestServiceServer。拿到MockRestServiceServer后,接着就需要添加请求断言和返回响应,然后进行验证。
RequestMatcher/MockRestRequestMatchers
RequestMatcher用于验证请求信息的验证器,即RestTemplate发送的请求的URI、请求方法、请求的Body体内容等等;spring mvc测试框架提供了很多***RequestMatchers来满足测试需求;类似于《Spring MVC测试框架详解——服务端测试》中的***ResultMatchers;注意这些***RequestMatchers并不是ResultMatcher的子类,而是返回RequestMatcher实例的。Spring mvc测试框架为了测试方便提供了MockRestRequestMatchers静态工厂方法方便操作;具体的API如下:
RequestMatcher anything():即请求可以是任何东西;
RequestMatcher requestTo(final Matcher<String> matcher)/RequestMatcher requestTo(final String expectedUri)/RequestMatcher requestTo(final URI uri):请求URI必须匹配某个Matcher/uri字符串/URI;
RequestMatcher method(final HttpMethod method):请求方法必须匹配某个请求方法;
RequestMatcher header(final String name, final Matcher<? super String>... matchers)/RequestMatcher header(final String name, final String... expectedValues):请求头必须匹配某个Matcher/某些值;
ContentRequestMatchers content():获取内容匹配器,然后可以通过如contentType(String expectedContentType)进行ContentType匹配等,具体请参考javadoc;
JsonPathRequestMatchers jsonPath(String expression, Object ... args)/RequestMatcher jsonPath(String expression, Matcher<T> matcher):获取Json路径匹配器/直接进行路径匹配,具体请参考javadoc;
XpathRequestMatchers xpath(String expression, Object... args)/XpathRequestMatchers xpath(String expression, Map<String, String> namespaces, Object... args):获取Xpath表达式匹配器/直接进行Xpath表达式匹配,具体请参考javadoc;
ResponseCreator/MockRestResponseCreators
ResponseCreator用于创建返回给客户端的响应信息,spring mvc提供了静态工厂方法MockRestResponseCreators进行操作;具体的API如下:
DefaultResponseCreator withSuccess() :返回给客户端200(OK)状态码响应;
DefaultResponseCreator withSuccess(String body, MediaType mediaType)/DefaultResponseCreator withSuccess(byte[] body, MediaType contentType)/DefaultResponseCreator withSuccess(Resource body, MediaType contentType):返回给客户端200(OK)状态码响应,且返回响应内容体和MediaType;
DefaultResponseCreator withCreatedEntity(URI location):返回201(Created)状态码响应,并返回响应头“Location=location";
DefaultResponseCreator withNoContent() :返回204(NO_CONTENT)状态码响应;
DefaultResponseCreator withBadRequest() :返回400(BAD_REQUEST)状态码响应;
DefaultResponseCreator withUnauthorizedRequest() :返回401(UNAUTHORIZED)状态码响应;
DefaultResponseCreator withServerError() :返回500(SERVER_ERROR)状态码响应;
DefaultResponseCreator withStatus(HttpStatus status):设置自定义状态码;
对于DefaultResponseCreator还提供了如下API:
DefaultResponseCreator body(String content) /DefaultResponseCreator body(byte[] content)/DefaultResponseCreator body(Resource resource):内容体响应,对于String content 默认是UTF-8编码的;
DefaultResponseCreator contentType(MediaType mediaType) :响应的ContentType;
DefaultResponseCreator location(URI location) :响应的Location头;
DefaultResponseCreator headers(HttpHeaders headers):设置响应头;
2.5 测试示例
测试查找
请参考之前的testFindById;
测试新增
提交JSON数据进行新增
@Test
public void testSaveWithJson() throws Exception {
User user = new User();
user.setId(1L);
user.setName("zhang");
String userJson = objectMapper.writeValueAsString(user);
String uri = baseUri;
String createdLocation = baseUri + "/" + 1;
mockServer
.expect(requestTo(uri)) //验证请求URI
.andExpect(jsonPath("$.name").value(user.getName())) //验证请求的JSON数据
.andRespond(withCreatedEntity(URI.create(createdLocation)).body(userJson).contentType(MediaType.APPLICATION_JSON)); //添加响应信息
restTemplate.setMessageConverters(Arrays.<HttpMessageConverter<?>>asList(new MappingJackson2HttpMessageConverter()));
ResponseEntity<User> responseEntity = restTemplate.postForEntity(uri, user, User.class);
assertEquals(createdLocation, responseEntity.getHeaders().get("Location").get(0));
assertEquals(HttpStatus.CREATED, responseEntity.getStatusCode());
assertEquals(user, responseEntity.getBody());
mockServer.verify();
}
提交XML数据进行新增
@Test
public void testSaveWithXML() throws Exception {
User user = new User();
user.setId(1L);
user.setName("zhang");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
marshaller.marshal(user, new StreamResult(bos));
String userXml = bos.toString();
String uri = baseUri;
String createdLocation = baseUri + "/" + 1;
mockServer
.expect(requestTo(uri)) //验证请求URI
.andExpect(xpath("/user/name/text()").string(user.getName())) //验证请求的JSON数据
.andRespond(withCreatedEntity(URI.create(createdLocation)).body(userXml).contentType(MediaType.APPLICATION_XML)); //添加响应信息
restTemplate.setMessageConverters(Arrays.<HttpMessageConverter<?>>asList(new Jaxb2RootElementHttpMessageConverter()));
ResponseEntity<User> responseEntity = restTemplate.postForEntity(uri, user, User.class);
assertEquals(createdLocation, responseEntity.getHeaders().get("Location").get(0));
assertEquals(HttpStatus.CREATED, responseEntity.getStatusCode());
assertEquals(user, responseEntity.getBody());
mockServer.verify();
}
测试修改
@Test
public void testUpdate() throws Exception {
User user = new User();
user.setId(1L);
user.setName("zhang");
String uri = baseUri + "/{id}";
mockServer
.expect(requestTo(uri)) //验证请求URI
.andExpect(jsonPath("$.name").value(user.getName())) //验证请求的JSON数据
.andRespond(withNoContent()); //添加响应信息
restTemplate.setMessageConverters(Arrays.<HttpMessageConverter<?>>asList(new MappingJackson2HttpMessageConverter()));
ResponseEntity responseEntity = restTemplate.exchange(uri, HttpMethod.PUT, new HttpEntity<>(user), (Class) null, user.getId());
assertEquals(HttpStatus.NO_CONTENT, responseEntity.getStatusCode());
mockServer.verify();
}
测试删除
@Test
public void testDelete() throws Exception {
String uri = baseUri + "/{id}";
Long id = 1L;
mockServer
.expect(requestTo(baseUri + "/" + id)) //验证请求URI
.andRespond(withSuccess()); //添加响应信息
ResponseEntity responseEntity = restTemplate.exchange(uri, HttpMethod.DELETE, HttpEntity.EMPTY, (Class) null, id);
assertEquals(HttpStatus.OK, responseEntity.getStatusCode());
mockServer.verify();
}
通过Mock Server的最大好处是不需要启动服务器,且不需要服务预先存在就可以测试;如果服务已经存在,通过Spring Boot进行测试也是个不错的选择。
再来回顾下测试步骤
1、准备测试环境
首先创建RestTemplate,然后通过MockRestServiceServer.createServer(restTemplate)创建一个Mock Server,其会自动设置restTemplate的requestFactory为RequestMatcherClientHttpRequestFactory(restTemplate发送请求时都通过ClientHttpRequestFactory创建ClientHttpRequest)。
2、调用API
即restTemplate.getForEntity(uri, User.class, id)访问rest web service;
3、断言验证
3.1、客户端请求断言验证
如mockServer.expect(requestTo(requestUri)).andExpect(method(HttpMethod.GET)):即会验证之后通过restTemplate发送请求的uri是requestUri,且请求方法是GET;
3.2、服务端响应断言验证
首先通过mockServer.andRespond(withSuccess(new ObjectMapper().writeValueAsString(user), MediaType.APPLICATION_JSON));返回给客户端响应信息;
然后restTemplate就可以得到ResponseEntity,之后就可以通过断言进行验证了;
4、 卸载测试环境
对于单元测试步骤请参考:加速Java应用开发速度3——单元/集成测试+CI。
测试示例请参考
https://github.com/zhangkaitao/spring4-showcase/tree/master/spring-mvc-test/src/test/java/com/sishuok/mvc/client
欢迎加入spring群134755960进行交流。
相关推荐
上文我们利用Spring rmi实现了Spring的远程访问(Spring 实现远程访问详解——rmi),本文主要讲解利用HttpInvoke实现远程访问。 Spring httpInvoker使用标准java序列化机制,通过Http暴露业务服务。如果你的参数和...
本书共计10章,分别介绍了快速搭建Spring Web应用、精通MVC结构、URL映射、文件上传与错误处理、创建Restful应用、保护应用、单元测试与验收测试、优化请求、将Web应用部署到云等内容,循序渐进地讲解了Spring MVC4...
Spring MVC 框架搭建及详解 适合郁闷程序员 超级有用
spring MVC框架用于web应用快速开发的一个模块。作为当今最主流的web开发框架,开发技能相当热门,值得大家掌握应用。
Spring MVC+Mybatis整合详解——资源自Linux公社
前几章我们分别利用spring rmi、httpinvoker、httpclient、webservice技术实现不同服务器间的远程访问。本章我将通过spring jms和activemq实现单Web项目服务器间异步访问和多Web项目...6) Spring mvc配置 7) 实例测试
spring mvc web框架
SSM框架的配置搭建 spring、 spring mvc、 mybatis 整合详解
spring mvc轻量级框架搭建,依赖全面jar文件包。下载解压直接将jar文件复制到工程中的lib中。
Spring Web Services 是基于 Spring 框架的 Web 服务框架,主要侧重于基于文档驱动的Web服务,提供 SOAP 服务开发,允许通过多种方式创建 Web 服务。本章利用Apache CXF构建和开发webservice. 1. webservice远程...
Spring为各种远程访问技术提供集成工具类。Spring远程访问通过使用普通POJOs,能更容易的开发远程访问服务。目前,Spring远程访问的主要技术如下: 1. 远程调用RMI(Remote Method Invocation): 通过使用 ...
测试-mvc概述使用 Spring MVC 测试框架进行测试。 该项目的目标是证明: 如何使用 DispatcherServlet 为控制器编写单元测试来处理请求,从而在不需要运行 Servlet 容器的情况下近似完整的集成测试; 如何编写集成...
Java EE 框架整合开发⼊⻔到实战——Spring+Spring MVC+MyBatis(微课版)课后习题答案.pdf
介绍Spring MVC框架以及使用套路
spring MVC配置详解
spring mvc源码框架,绝对可以运行。
spring + spring mvc + mybatis框架,只实现了一个添加用户,验证用户功能
spring mvc,spring,hibernate框架开发
简单的描述了spring mvc的配置示例,不包括注解方法和示例代码