前面介紹過了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));
方法調用鏈圖:
看不懂這圖,可以先不用急,看完下面rest-assured原生的api如何使用後,再來看這張圖可能對方法間調用關係就明白了。
爲了更好的演示,創建個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);
}
}