毫无疑问, JAX-RS是一项杰出的技术。 即将发布的规范JAX-RS 2.0带来了更多的强大功能,尤其是在客户端API方面。 今天的帖子的主题是JAX-RS服务的集成测试。 有很多出色的测试框架,例如REST可以确保提供帮助,但是我要展示的方式是使用富有表现力的BDD风格。 这是我的意思的示例:

Create new person with email <a@b.com>Given REST client for application deployed at http://localhost:8080When I do POST to rest/api/people?email=a@b.com&firstName=Tommy&lastName=KnockerThen I expect HTTP code 201

看起来像现代BDD框架的典型Given / When / Then风格。 使用静态编译语言,我们在JVM上能达到多近的距离? 事实证明,这要归功于良好的specs2测试工具。

值得一提的是, specs2是一个Scala框架。 尽管我们将编写一些Scala ,但是我们将以非常有经验的Java开发人员熟悉的非常直观的方式来完成它。 测试中的JAX-RS服务是我们在上一篇文章中开发的 。 这里是:

package com.example.rs;import java.util.Collection;import javax.inject.Inject;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;import com.example.model.Person;
import com.example.services.PeopleService;@Path( '/people' )
public class PeopleRestService {@Inject private PeopleService peopleService;@Produces( { MediaType.APPLICATION_JSON } )@GETpublic Collection< Person > getPeople( @QueryParam( 'page') @DefaultValue( '1' ) final int page ) {return peopleService.getPeople( page, 5 );}@Produces( { MediaType.APPLICATION_JSON } )@Path( '/{email}' )@GETpublic Person getPeople( @PathParam( 'email' ) final String email ) {return peopleService.getByEmail( email );}@Produces( { MediaType.APPLICATION_JSON  } )@POSTpublic Response addPerson( @Context final UriInfo uriInfo,@FormParam( 'email' ) final String email, @FormParam( 'firstName' ) final String firstName, @FormParam( 'lastName' ) final String lastName ) {peopleService.addPerson( email, firstName, lastName );return Response.created( uriInfo.getRequestUriBuilder().path( email ).build() ).build();}@Produces( { MediaType.APPLICATION_JSON  } )@Path( '/{email}' )@PUTpublic Person updatePerson( @PathParam( 'email' ) final String email, @FormParam( 'firstName' ) final String firstName, @FormParam( 'lastName' )  final String lastName ) {final Person person = peopleService.getByEmail( email );  if( firstName != null ) {person.setFirstName( firstName );}if( lastName != null ) {person.setLastName( lastName );}return person;     }@Path( '/{email}' )@DELETEpublic Response deletePerson( @PathParam( 'email' ) final String email ) {peopleService.removePerson( email );return Response.ok().build();}
}

非常简单的JAX-RS服务来管理人员。 Java实现提供并支持所有基本的HTTP动词 : GETPUTPOSTDELETE 。 为了完整起见,让我也包括服务层的一些方法,因为这些方法引起我们关注的一些例外。

package com.example.services;import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;import org.springframework.stereotype.Service;import com.example.exceptions.PersonAlreadyExistsException;
import com.example.exceptions.PersonNotFoundException;
import com.example.model.Person;@Service
public class PeopleService {private final ConcurrentMap< String, Person > persons = new ConcurrentHashMap< String, Person >(); // ...public Person getByEmail( final String email ) {final Person person = persons.get( email );  if( person == null ) {throw new PersonNotFoundException( email );}return person;}public Person addPerson( final String email, final String firstName, final String lastName ) {final Person person = new Person( email );person.setFirstName( firstName );person.setLastName( lastName );if( persons.putIfAbsent( email, person ) != null ) {throw new PersonAlreadyExistsException( email );}return person;}public void removePerson( final String email ) {if( persons.remove( email ) == null ) {throw new PersonNotFoundException( email );}}
}

基于ConcurrentMap的非常简单但可行的实现。 如果请求的电子邮件的人不存在, 则会引发PersonNotFoundException 。 分别在已经存在带有请求电子邮件的人的情况下引发PersonAlreadyExistsException 。 这些异常中的每一个在HTTP代码中都有一个对应项: 404 NOT FOUND409 CONFLICT 。 这就是我们告诉JAX-RS的方式:

package com.example.exceptions;import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;public class PersonAlreadyExistsException extends WebApplicationException {private static final long serialVersionUID = 6817489620338221395L;public PersonAlreadyExistsException( final String email ) {super(Response.status( Status.CONFLICT ).entity( 'Person already exists: ' + email ).build());}
}
package com.example.exceptions;import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;public class PersonNotFoundException extends WebApplicationException {private static final long serialVersionUID = -2894269137259898072L;public PersonNotFoundException( final String email ) {super(Response.status( Status.NOT_FOUND ).entity( 'Person not found: ' + email ).build());}
}

完整的项目托管在GitHub上 。 让我们结束无聊的部分,然后转到最甜蜜的部分: BDD 。 如文档中所述, specs2对Given / When / Then样式提供了很好的支持也就不足为奇了。 因此,使用specs2 ,我们的测试用例将变成这样:

'Create new person with email <a@b.com>' ^ br^'Given REST client for application deployed at ${http://localhost:8080}' ^ client^'When I do POST to ${rest/api/people}' ^ post(Map('email' -> 'a@b.com', 'firstName' -> 'Tommy', 'lastName' -> 'Knocker'))^'Then I expect HTTP code ${201}'  ^ expectResponseCode^'And HTTP header ${Location} to contain ${http://localhost:8080/rest/api/people/a@b.com}' ^ expectResponseHeader^

还不错,但是那些^brclientpostExpectResponseCodeExpectResponseHeader是什么? ^br只是用于支持Given / When / Then链的一些specs2糖。 其他的postExpectResponseCodeExpectResponseHeader只是我们定义用于实际工作的几个函数/变量。 例如, 客户端是一个新的JAX-RS 2.0客户端,我们以此创建(使用Scala语法):

val client: Given[ Client ] = ( baseUrl: String ) => ClientBuilder.newClient( new ClientConfig().property( 'baseUrl', baseUrl ) )

baseUrl来自Given定义本身,它包含在$ {…}构造中。 此外,我们可以看到Given定义具有很强的类型: Given [Client] 。 稍后我们将看到, WhenThen的情况相同 ,它们确实具有相应的强类型when [T,V]Then [V]

流程如下所示:

  • 给定定义开始,该定义返回Client 。
  • 继续何时定义,该定义接受来自给定的 客户并返回响应
  • 最后是数量的Then定义,这些定义接受来自When的 响应并检查实际期望

这是帖子定义的样子(本身就是When [Client,Response] ):

def post( values: Map[ String, Any ] ): When[ Client, Response ] = ( client: Client ) => ( url: String ) =>  client.target( s'${client.getConfiguration.getProperty( 'baseUrl' )}/$url' ).request( MediaType.APPLICATION_JSON ).post( Entity.form( values.foldLeft( new Form() )( ( form, param ) => form.param( param._1, param._2.toString ) ) ),classOf[ Response ] )

最后是ExpectResponseCodeExpectResponseHeader ,它们非常相似,并且具有相同的类型Then [Response]

val expectResponseCode: Then[ Response ] = ( response: Response ) => ( code: String ) => response.getStatus() must_== code.toInt                           val expectResponseHeader: Then[ Response ] = ( response: Response ) => ( header: String, value: String ) =>        response.getHeaderString( header ) should contain( value )

又一个示例,根据JSON有效负载检查响应内容:

'Retrieve existing person with email <a@b.com>' ^ br^'Given REST client for application deployed at ${http://localhost:8080}' ^ client^'When I do GET to ${rest/api/people/a@b.com}' ^ get^'Then I expect HTTP code ${200}' ^ expectResponseCode^'And content to contain ${JSON}' ^ expectResponseContent('''{'email': 'a@b.com', 'firstName': 'Tommy', 'lastName': 'Knocker' }            ''')^

这次我们使用以下get实现来执行GET请求:

val get: When[ Client, Response ] = ( client: Client ) => ( url: String ) =>  client.target( s'${client.getConfiguration.getProperty( 'baseUrl' )}/$url' ).request( MediaType.APPLICATION_JSON ).get( classOf[ Response ] )

尽管specs2具有丰富的匹配器集,可对JSON有效负载执行不同的检查,但我在Scala中使用了spray-json ,这是一种轻量级,干净且简单的JSON实现(的确如此!),这是ExpectResponseContent实现:

def expectResponseContent( json: String ): Then[ Response ] = ( response: Response ) => ( format: String ) => {format match { case 'JSON' => response.readEntity( classOf[ String ] ).asJson must_== json.asJsoncase _ => response.readEntity( classOf[ String ] ) must_== json}
}

最后一个示例(对现有电子邮件进行POST ):

'Create yet another person with same email <a@b.com>' ^ br^'Given REST client for application deployed at ${http://localhost:8080}' ^ client^'When I do POST to ${rest/api/people}' ^ post(Map( 'email' -> 'a@b.com' ))^'Then I expect HTTP code ${409}' ^ expectResponseCode^'And content to contain ${Person already exists: a@b.com}' ^ expectResponseContent^

看起来很棒! 好的,富有表现力的BDD ,使用强类型和静态编译! 当然,可以使用JUnit集成,并且可以与Eclipse一起很好地工作。

不要忘记自己的specs2报告(由maven-specs2-plugin生成): mvn clean test

请在GitHub上查找完整的项目。 另外,请注意,由于我使用的是最新的JAX-RS 2.0里程碑(最终草案),因此发布API时可能会有所变化。

参考: Andriy Redko {devmind}博客上的JCG合作伙伴 Andrey Redko 使用Specs2和客户端API 2.0进行了富有表现力的JAX-RS集成测试 。

翻译自: https://www.javacodegeeks.com/2013/03/expressive-jax-rs-integration-testing-with-specs2-and-client-api-2-0.html

使用Specs2和客户端API 2.0进行富有表现力的JAX-RS集成测试相关推荐

  1. specs.4.8.gz_使用Specs2和客户端API 2.0进行富有表现力的JAX-RS集成测试

    specs.4.8.gz 毫无疑问, JAX-RS是一项杰出的技术. 即将发布的规范JAX-RS 2.0带来了更多的强大功能,尤其是在客户端API方面. 今天的帖子的主题是JAX-RS服务的集成测试. ...

  2. 通过 Jersey Http请求头,Http响应头,客户端 API 调用 REST 风格的 Web 服务

    原地址:http://blog.csdn.net/li575098618/article/details/47853263 Jersey 1.0 是一个开源的.可以用于生产环境的 JAX-RS(RES ...

  3. Stormpath发布了简化移动和前端身份验证的客户端API

    身份验证.授权.社交登录,以及其他用户管理相关API服务供应商Stormpath,近日发布了一套意在简化移动和前端身份验证与注册过程的全新客户端API. \\ 这一全新API完善了Stormpath侧 ...

  4. sqoop2 java api实现_Sqoop2 Java客户端API指南

    原文连接:http://sqoop.apache.org/docs/1.99.6/ClientAPI.html Sqoop Java客户端API指南 这篇文章秒描述了额如何在外部应用中使用sqoop ...

  5. mvvm 后端_ZK实际应用:MVVM –与ZK客户端API一起使用

    mvvm 后端 在以前的文章中,我们已经使用ZK的MVVM实现了以下功能: 将数据加载到表中 使用表单绑定保存数据 删除条目并以编程方式更新视图 ZK MVVM和ZK MVC实现方式之间的主要区别是, ...

  6. Java EE 7中的WebSocket客户端API

    在本文中,让我们探索谈论较少的Web Socket Client API,以及如何在Java EE 7容器本身中利用它. Web套接字服务器API规则 JSR 356的服务器端API(Java的Web ...

  7. ZK实际应用:MVVM –与ZK客户端API一起使用

    在之前的文章中,我们使用ZK的MVVM实现了以下功能: 将数据加载到表中 使用表单绑定保存数据 删除条目并以编程方式更新视图 ZK MVVM和ZK MVC实现方式之间的主要区别是,我们不直接在cont ...

  8. 【转载】Elasticsearch客户端API使用Demo

    Elasticsearch客户端API使用Demo, 转载自官方文档, 以索引雇员文档为示例, 在命令行使用curl演示了一系列的Restful API操作. 1.索引雇员文档 第一个业务需求就是存储 ...

  9. 生成用于ASP.NET Web API的C#客户端API

    目录 介绍 主要特征 主要好处 背景 推定(Presumptions) 使用代码 步骤0:将NuGet软件包WebApiClientGen安装到Web MVC/API项目 步骤1:建立.NET Cli ...

最新文章

  1. 百度大脑发挥AI“头雁效应” 王海峰:在AI时代共同推动社会智能化升级
  2. ASp.net 剖析三层架构
  3. 阮一峰react demo代码研究的学习笔记 - ReactMount.render
  4. C#关于读写INI文件
  5. 用limit 实现java的简单分页
  6. 【数据结构基础】-串-顺序结构的基本操作实现
  7. 教你29招,让你在社交,职场上人人对你刮目相看
  8. polycom安卓手机客户端_安卓 emoji 表情:全变了
  9. element 搜索匹配_如何在Element-ui中实现一个远程搜索功能
  10. F5 V9 利用SCCP实现带外管理及重装系统
  11. Psam_ISO7816
  12. python flink_如何在 Apache Flink 中使用 Python API?
  13. Java多线程_生产者消费者模式1
  14. 越智能?越危险?技术到底应不应该进步?
  15. 三校生计算机高考试卷及答案,云南三校生计算机专业课高考试卷3
  16. 华为手机android是什么意思,华为手机里的文件夹表示什么意思?
  17. ipod nano 无法添加mp4视频 电影失败解决方法
  18. PQ 8.05中文版硬盘物理分区与删除分区(图文详解)
  19. Android 支付宝开发错误总结
  20. cotex单片机寄存器(cm3为例)

热门文章

  1. django mysql 创建表_关于 django ORM 中,数据库建表方式的问题
  2. python菱形画法解释_用Python画棱形
  3. thinking-in-java(20)注解
  4. java设计模式适配器模式_Java中的适配器设计模式
  5. spring框架介绍_Spring框架介绍
  6. java8返回单个号码_如何在单个API中支持Java 6、8、9
  7. 在Spring中使用@ResponseStatus获取Http状态
  8. junit测试报告生成_这是东西:jUnit:动态测试生成
  9. 使用Java 8 Lambda表达式将实体映射到DTO
  10. Java Stream:计数始终是计数吗?