rest-assured实战
前面介绍过了rest-assured的官网wiki翻译,这篇我们来实战使用下
版本选用
4.2.0
,目前最新的是4.3.0
,但有groovy版本bug。(在使用开源组件时,一定要注意最新版的风险)
使用4.3.0时遇到的bug AbstractMethodError
技巧
在使用开源组件时,遇到bug,可以第一时间去对应的issues里查看下,有时比baidu/google效率高的多
1.常用语法组合
given()--when()--then()
given()--expect()--when()
given
里设置参数、头、认证when()
里请求rest接口,get、post、put、delete等expect()
和then()
里验证结果。
given()–expect()–when()
given().
param("x", "y").
expect().
statusCode(400).
body("lotto.lottoId", equalTo(6)).
when().
get("/lotto");
given()–when()–then()
given().
param("x", "y").
when().
get("/lotto").
then().
statusCode(400).
body("lotto.lottoId", equalTo(6));
方法调用链图:
为了更好的演示,创建个springboot的web项目,为了简化,只有controller层和domain层,没有service和dao的逻辑。
如何创建springboot项目,就不多介绍了,不会可以参照 iworkh-springboot-helloworld
1.业务代码
1-1.交互类
JsonDataResult类主要为了将返回数据封装成统一固定格式,返回。
public class JsonDataResult<T> {
protected boolean success;
protected String message;
protected int errorCode = 0;
@JsonProperty("result")
protected T data;
...省略了setgeter...
}
1-2.实体类
public class UserVo {
private int id;
private String name;
private Date birthday;
private boolean vip;
...省略了setgeter...
}
不多解释,一个bean
1-3.controller
UserController类里定义了增删改查接口,并@RestController
注解,返回值是json
@RestController
@RequestMapping("/api/user")
public class UserController {
@PostMapping("/createUserByParam")
public JsonDataResult<Boolean> createUserByParam(UserVo userVo) {
JsonDataResult<Boolean> result = new JsonDataResult<>();
userVo.setId(new Random().nextInt(50));
result.setSuccess(true);
result.setData(true);
return result;
}
@PostMapping("/createUserByJson")
public JsonDataResult<Boolean> createUserByJson(@RequestBody UserVo userVo) {
JsonDataResult<Boolean> result = new JsonDataResult<>();
userVo.setId(new Random().nextInt(100));
result.setSuccess(true);
result.setData(true);
return result;
}
@GetMapping("/{id}")
public JsonDataResult<UserVo> getUser(@PathVariable int id) {
JsonDataResult<UserVo> result = new JsonDataResult<>();
String[] hobbies = {"football", "sing"};
//从数据库查询,省略
UserVo user = new UserVo(id, "iworkh" + id, System.currentTimeMillis(), true, Arrays.asList(hobbies));
result.setSuccess(true);
result.setData(user);
return result;
}
@PutMapping
public JsonDataResult<UserVo> updateUser(@RequestBody UserVo userVo) {
JsonDataResult<UserVo> result = new JsonDataResult<>();
//从数据库删除,省略
result.setSuccess(true);
result.setData(userVo);
return result;
}
@DeleteMapping("/{id}")
public JsonDataResult<Boolean> delteUser(@PathVariable int id) {
JsonDataResult<Boolean> result = new JsonDataResult<>();
//从数据库删除,省略
result.setSuccess(true);
result.setData(true);
return result;
}
}
2.原生rest-assured API
RestAssured
类是很关键的一个类,打开发现很多方法,和默认配置,这里提下几个默认配置
DEFAULT_URI
: 默认值是http://localhost
DEFAULT_PORT
: 默认值是8080
所以,当我们端口号不是默认值时,我们也得修改配置。(比如:
RestAssured.port = 9090;
)
为了简单演示,一些认证都不打开了。直接演示调用rest接口测试。
引入依赖
<dependencies>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>${rest-assured.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>json-path</artifactId>
<groupId>io.rest-assured</groupId>
</exclusion>
<exclusion>
<artifactId>xml-path</artifactId>
<groupId>io.rest-assured</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>json-path</artifactId>
<version>${rest-assured.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>xml-path</artifactId>
<version>${rest-assured.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
其中rest-assured的版本是4.2.0
<rest-assured.version>4.2.0</rest-assured.version>
2-1.插入
2-1-1.queryParams方式
queryParams:即通过url后面加参数方式传递
@Test
public void test001CreateUserByUrl() {
String[] hobbies = {"football", "sing"};
UserVo zhangsanUser = new UserVo(1, "zhangsan", System.currentTimeMillis(), false, Arrays.asList(hobbies));
given().
queryParams(BeanMapTool.beanToMap(zhangsanUser)).
when().
post("/api/user/createUserByParam").
then().
statusCode(200).
body("result", equalTo(true));
}
XxxParmas方法需要的是一个Map对象,所以可以使用Map初始化值传递。
BeanMapTool.beanToMap()是个工具类,可以将bean转化为Map,使用的是org.springframework.cglib.beans.BeanMap
来完成,文章最后会给出此工具类
2-1-2.params方式
queryParams:即通过post参数方式
@Test
public void test001CreateUserByParam() {
String[] hobbies = {"football", "sing"};
UserVo zhangsanUser = new UserVo(1, "zhangsan", System.currentTimeMillis(), false, Arrays.asList(hobbies));
given().
params(BeanMapTool.beanToMap(zhangsanUser)).
when().
post("/api/user/createUserByParam").
then().
statusCode(200).
body("result", equalTo(true));
}
2-1-3.formParams方式
formParams:即通过post form表单方式
@Test
public void test001CreateUserByFormParam() {
String[] hobbies = {"football", "sing"};
UserVo zhangsanUser = new UserVo(1, "zhangsan", System.currentTimeMillis(), false, Arrays.asList(hobbies));
given().
formParams(BeanMapTool.beanToMap(zhangsanUser)).
when().
post("/api/user/createUserByParam").
then().
statusCode(200).
body("result", equalTo(true));
}
2-1-4.json方式
contentType:指定json格式,并body传数据。(body的值,不一定json字符串,是对象也可以)
@Test
public void test003CreateUserByJson() {
String[] hobbies = {"football", "sing"};
UserVo zhangsanUser = new UserVo(1, "zhangsan", System.currentTimeMillis(), false, Arrays.asList(hobbies));
given().
contentType(ContentType.JSON).body(zhangsanUser).
when().
post("/api/user/createUserByJson").
then().
statusCode(200).
body("result", equalTo(true));
}
2-2.更新
@Test
public void test002Update(){
String[] hobbies = {"football", "play games"};
UserVo zhangsanUser = new UserVo(1, "zhangsan", System.currentTimeMillis(), false, Arrays.asList(hobbies));
given().
contentType(ContentType.JSON).body(zhangsanUser).
when().
put("/api/user/").
then().
statusCode(200).
body("result.hobbies", hasItems("football", "play games"));
}
更新比较简单,使用
put
来更新,验证使用hasItems
来验证多个结果
2-3.删除
@Test
public void test003Delete(){
given().
when().
delete("/api/user/{id}",1).
then().
statusCode(200).
body("result", equalTo(true));
}
删除更简单,使用
delete
2-4.查询
查询留最后,因为这时我们使用最多,而且使用技巧最多的地方
2-4-1.body验证
@Test
public void test004GetUserPathParam() {
given().
pathParam("id", 1).
when().
get("/api/user/{id}").
then().
statusCode(200).
body("result.name", equalTo("iworkh1"));
}
直接通过body的path提交值,验证
2-4-2.Response值验证
@Test
public void test004GetUserExactResponse() {
Response response=
given().
expect().
statusCode(200).
when().
get("/api/user/2");
String name = response.path("result.name");
Assert.assertThat(name, equalTo("iworkh2"));
}
直接根据Response的返回值,自己解析Response里header、body等来验证
2-4-3.转化对象
我们还可以直接将Response转化为对象,来验证处理
通过Response的as方法,参数是类型
- 类型是普通类:
Xxx.class
即可 - 类型是泛型:
new TypeRef<Xxx>(){}
来转化为需要的对象
@Test
public void test004GetUserToBean() {
Response response = RestAssuredTool.get("/api/user/3");
JsonDataResult<UserVo> userVoJsonResult = response.as(new TypeRef<JsonDataResult<UserVo>>() {});
System.out.println(userVoJsonResult.getData());
Assert.assertThat(userVoJsonResult.getData().getName(), equalTo("iworkh3"));
}
3.封装rest-assured API
原生rest-assured API使用起来,非常的灵活,但是对于开发者而言,一直...
(Fluent风格)的方法调用比较麻烦。
我比较倾向于,调用一个方法,把需要参数都传过去,就结束了,不需要关系底层太多调用,因此对原生的API做下封装
封装好的好处
- 认证在封装里做,不用在测试代码中去验证
- 业务测试代码更关注业务,而不用太关注rest-assured的使用
缺点
- 被封装后的方法,不够灵活。(不灵活,那就原生API,只要留出接口就行)
文章最后,给出封装好的
RestAssuredTool
类,当然这封装的不一定满足所有场合,也不是最好的。(大家可根据自己的需求来封装,这只抛砖引玉下)
调用封装后的测试类
package com.iworkh.test.restassured.controller;
import com.iworkh.test.restassured.domain.vo.JsonDataResult;
import com.iworkh.test.restassured.domain.vo.UserVo;
import com.iworkh.test.restassured.utils.BeanMapTool;
import com.iworkh.test.restassured.utils.RestAssuredTool;
import io.restassured.common.mapper.TypeRef;
import io.restassured.response.Response;
import org.junit.Assert;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
import java.util.Arrays;
import java.util.Map;
import static org.hamcrest.Matchers.equalTo;
/**
* UserController测试类
*
* @author: iworkh-沐雨云楼
* @date: 2020-06-18
*/
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class UserControllerTest {
private String userBaseUrl = "/api/user";
@Test
public void test001CreateUserByParam() {
String[] hobbies = {"football", "sing"};
UserVo zhangsanUser = new UserVo(1, "zhangsan", System.currentTimeMillis(), false, Arrays.asList(hobbies));
Response resp = RestAssuredTool.postWithParams(userBaseUrl + "/createUserByParam",
BeanMapTool.beanToMap(zhangsanUser));
RestAssuredTool.validateStatusCode(resp, 200);
RestAssuredTool.validateEqualTo(resp, "result", true);
}
@Test
public void test001CreateUserByJson() {
String[] hobbies = {"football", "sing"};
UserVo zhangsanUser = new UserVo(1, "zhangsan", System.currentTimeMillis(), false, Arrays.asList(hobbies));
Response resp = RestAssuredTool.postWithJson(userBaseUrl + "/createUserByJson", zhangsanUser);
RestAssuredTool.validateStatusCode(resp, 200);
}
@Test
public void test002Upate() {
String[] hobbies = {"football", "play games"};
UserVo zhangsanUser = new UserVo(1, "zhangsan", System.currentTimeMillis(), false, Arrays.asList(hobbies));
Response response = RestAssuredTool.putWithJson(userBaseUrl, zhangsanUser);
RestAssuredTool.validateStatusCode(response, 200);
}
@Test
public void test003Delete() {
Response response = RestAssuredTool.delete(userBaseUrl + "/id");
RestAssuredTool.validateStatusCode(response, 200);
}
@Test
public void test004GetUser01() {
Response resp = RestAssuredTool.get(userBaseUrl + 1);
RestAssuredTool.validateHasItems(resp, "result.hobbies", "football", "sing");
}
@Test
public void test004GetUser02() {
Response response = RestAssuredTool.get(userBaseUrl + 2);
int id = response.path("result.id");
Assert.assertThat(id, equalTo(2));
}
@Test
public void test004GetUser03() {
Response response = RestAssuredTool.get(userBaseUrl + 3);
// 转化为JsonDataResult对象,不过data部分是map,再使用BeanMapTool工具可以转为对对应的对象
JsonDataResult<Map<String, ?>> userVoJsonDataResult = RestAssuredTool.asJsonDataResult(response);
System.out.println(userVoJsonDataResult.getData());
Assert.assertThat(userVoJsonDataResult.getData().get("name"), equalTo("iworkh3"));
try {
UserVo userVo = BeanMapTool.mapToBean(userVoJsonDataResult.getData(), UserVo.class);
Assert.assertThat(userVo.getName(), equalTo("iworkh3"));
} catch (IllegalAccessException | InstantiationException e) {
e.printStackTrace();
}
}
@Test
public void test004GetUser04() {
Response response = RestAssuredTool.get(userBaseUrl + 4);
// 通过泛型转化为需要的对象
JsonDataResult<UserVo> userVoJsonResult = RestAssuredTool.asGeneric(response,
new TypeRef<JsonDataResult<UserVo>>() {
});
Assert.assertThat(userVoJsonResult.getData().getName(), equalTo("iworkh4"));
}
}
代码中重要的地方都有注释,就不多解释了
4.工具类
4-1.BeanMapTool
bean转map工具类参照博客 工具类–bean和map互转
使用的是里面的BeanMapTool工具类
4-2.RestAssuredTool
需要扩展的几个点:
- port和baseURI修改成从配置文件读取
- 在初始化或者
restClient
时,将认证加上其他如何操作可以查看官网或者查看翻译的wiki rest-assured wiki翻译
package com.iworkh.test.restassured.utils;
import com.iworkh.test.restassured.domain.vo.JsonDataResult;
import io.restassured.RestAssured;
import io.restassured.common.mapper.TypeRef;
import io.restassured.http.ContentType;
import io.restassured.http.Headers;
import io.restassured.path.json.JsonPath;
import io.restassured.path.json.config.JsonPathConfig;
import io.restassured.response.Response;
import io.restassured.specification.RequestSpecification;
import java.util.Map;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItems;
/**
* RestAssuredTool工具类
*
* @author: iworkh-沐雨云楼
* @date: 2020-06-18
*/
public class RestAssuredTool {
static {
// 这可以修改为从配置文件读取
String siteBaseURI = "http://localhost";
int port = 8080;
RestAssured.baseURI = siteBaseURI;
RestAssured.port = port;
JsonPath.config = new JsonPathConfig("UTF-8");
}
public static RequestSpecification restClient() {
// 认证等操作,都可以在这统一处理
return given();
}
public static RequestSpecification restClientWithHeader(Headers headers) {
// 认证等操作,都可以在这统一处理
return given().headers(headers);
}
// get
public static Response get(String url) {
return restClient().get(url);
}
public static Response getWithParams(String url, Map<String, ?> params) {
return restClient().params(params).get(url);
}
public static Response getWithQueryParams(String url, Map<String, ?> params) {
return restClient().queryParams(params).get(url);
}
public static Response getWithFormParams(String url, Map<String, ?> params) {
return restClient().formParams(params).get(url);
}
public static <T> Response getWithJson(String url, T data) {
return restClient().contentType(ContentType.JSON).body(data).get(url);
}
// post
public static Response postWithParams(String url, Map<String, ?> params) {
return restClient().params(params).post(url);
}
public static Response postWithFormParams(String url, Map<String, ?> params) {
return restClient().formParams(params).post(url);
}
public static <T> Response postWithJson(String url, T data) {
return restClient().contentType(ContentType.JSON).body(data).post(url);
}
// put
public static Response putWithParams(String url, Map<String, ?> params) {
return restClient().params(params).put(url);
}
public static Response putWithFormParams(String url, Map<String, ?> params) {
return restClient().formParams(params).put(url);
}
public static <T> Response putWithJson(String url, T data) {
return restClient().contentType(ContentType.JSON).body(data).put(url);
}
// delete
public static Response delete(String url) {
return restClient().delete(url);
}
public static <T> Response deleteWithJson(String url, T data) {
return restClient().contentType(ContentType.JSON).body(data).delete(url);
}
// validate response
public static void validateStatusCode(Response response, int expectedStatusCode) {
response.then().statusCode(expectedStatusCode);
}
public static <T> void validateEqualTo(Response response, String path, T expectedValue) {
response.then().body(path, equalTo(expectedValue));
}
public static <T> void validateHasItems(Response response, String path, T... expectedValue) {
response.then().body(path, hasItems(expectedValue));
}
// convert
public static JsonDataResult<Map<String, ?>> asJsonDataResult(Response response) {
return response.as(new TypeRef<JsonDataResult<Map<String, ?>>>() {});
}
public static <T> T asGeneric(Response response, TypeRef<T> typeRef) {
return response.as(typeRef);
}
public static <T> T asCls(Response response, Class<T> cls) {
return response.as(cls);
}
}
转载请注明来源,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 157162006@qq.com
文章标题:rest-assured实战
字数:2.9k
本文作者:沐雨云楼
发布时间:2020-06-18, 17:51:56
最后更新:2020-09-12, 21:21:47
原始链接:https://iworkh.gitee.io/blog/2020/06/18/java-rest-assured-in-action/版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。