1. REST-assured 介绍
REST assured 框架被设计用来简化 REST API 的测试和验证,并受 Ruby 和 Groovy 等动态语言中使用的测试框架的较大影响。
REST-assured 有着对 HTTP 的强大的支持,支持 HTTP 的基本的请求方式,除此之外还有一些其他的特性。
在本指南中,我们将认识 REST-assured,并将使用 Hamcrest 进行断言。如果你还不熟悉Hamcrest,可以先看一下这篇文章:使用 Hamcrest 测试。
此外,要了解 REST-assured 更深层次的应用,你也可以查看其他文章:
- REST-assured with Groovy
- JSON Schema Validation with REST-assured
- Parameters, Headers and Cookies with REST-assured
现在,让我们通过一个简单的例子来深入 REST-assured。
2. 第一个 REST-assured 例子
在开始之前,现在测试中导入以下内容:
io.restassured.RestAssured.*
io.restassured.matcher.RestAssuredMatchers.*
org.hamcrest.Matchers.*
我们将使测试尽可能简单,并能够轻松访问到主要 API。 现在,让我们从一个简单的例子开始——一个基本的博彩系统,以下是一些游戏数据:
{
"id": "390",
"data": {
"leagueId": 35,
"homeTeam": "Norway",
"visitingTeam": "England",
},
"odds": [{
"price": "1.30",
"name": "1"
},
{
"price": "5.25",
"name": "X"
}]
}
假设这就是我们本地部署的 API http://localhost:8080/events?id=390. 的 JSON 格式的响应数据。
接下来我们使用 REST-assured 来验证 JSON 响应数据中的一些特征。
@Test
public void givenUrl_whenSuccessOnGetsResponseAndJsonHasRequiredKV_thenCorrect() {
get("/events?id=390").then().statusCode(200).assertThat()
.body("data.leagueId", equalTo(35));
}
上述代码中我们通过调用 /events?id=390 获取到了响应,并断言这个 JSON 响应中的 data.leaguedId 的值为 35。
接下来看一个更有趣的例子,假设你想断言 JSON 响应中的 odds 数组中是否包含 "1.30" 和 "5.25" 两个记录。
@Test
public void givenUrl_whenJsonResponseHasArrayWithGivenValuesUnderKey_thenCorrect() {
get("/events?id=390").then().assertThat()
.body("odds.price", hasItems("1.30", "5.25"));
}
3. REST-assured 环境搭建
如果你习惯使用 Maven 作为构建工具,可以在 pom.xml 中添加如下依赖:
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>3.3.0</version>
<scope>test</scope>
</dependency>
想要获取最新版本,可以查看这个链接
REST assured 使用了 Hamcrest 匹配器的来进行断言,因此我们还必须包括 Hamcrest 的依赖:
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<version>2.1</version>
</dependency>
想要获取最新版本,可以查看这个链接
4. 匿名 JSON ROOT 断言
将一个由基本元素而非对象组成的数组:
[1,2,3]
这个数组就被称为匿名的 JSON root,也就是说它没有键值对,但它仍然是有效的 JSON 数据。
在这种情况下,我们可以使用 $ 符号或空字符串(“”)作为路径来进行断言。假设我们通过http://localhost:8080/json 来提供一个服务,那么我们通过如下方式使用 REST-assured 进行断言:
when().get("/json").then().body("$", hasItems(1, 2, 3));
或者这样:
when().get("/json").then().body("", hasItems(1, 2, 3));
5. Floats 和 Doubles
当我们使用 REST-assured 来测试 REST 服务时,我们需要知道 JSON 响应中的 float 浮点数对应的是 float 数据类型。
float 类型的使用不能与 double 类型互换使用,这在 Jav a的许多场景中都是这样。
假设响应内容如下:
{
"odd": {
"price": "1.30",
"ck": 12.2,
"name": "1"
}
}
我们需要断言其中的 ck 的值:
get("/odd").then().assertThat().body("odd.ck", equalTo(12.2));
这个测试最终会失败,即使断言的值与响应的值相等。这是因为我们将一个 double 类型和一个 float 类型进行比较。
为了使测试能够执行成功,我们需要将 equalTo 方法的操作数显式指定为浮点类型,如下所示:
get("/odd").then().assertThat().body("odd.ck", equalTo(12.2f));
6.指定请求方式
通常,我们会通过调用一个方法(如get())来执行请求,该方法对应于我们想要使用的请求方法。 此外,我们还可以使用 request() 方法指定 HTTP 请求方式:
@Test
public void whenRequestGet_thenOK(){
when().request("GET", "/users/eugenp").then().statusCode(200);
}
上述例子等同于直接使用 get() 方法。
同样的,我们可以在请求中发送 HEAD, CONNECT 和 OPTIONS:
@Test
public void whenRequestHead_thenOK() {
when().request("HEAD", "/users/eugenp").then().statusCode(200);
}
POST请求也遵循类似的语法,我们可以使用 with() 和 body() 方法指定 body。 因此,可以通过发送 POST 请求创建新的 Odd:
@Test
public void whenRequestedPost_thenCreated() {
with().body(new Odd(5.25f, 1, 13.1f, "X"))
.when()
.request("POST", "/odds/new")
.then()
.statusCode(201);
}
作为请求体发送的 Odd 对象会自动转换为 JSON。我们还可以通过 POST 请求体发送的任何字符串格式数据。
7. 默认配置
我们可以在测试中配置一系列的默认值:
@Before
public void setup() {
RestAssured.baseURI = "https://api.github.com";
RestAssured.port = 443;
}
这里我们为请求设置了一个基本 URI 和端口。除此之外,我们还可以配置基本路径、根路径和身份验证信息。
注意:我们还可以使用以下方法重置为标准 REST-assured 的默认值:
RestAssured.reset();
8. 衡量响应时间
接下来我们将通过 time() 和 timeln() 方法来测量响应时间:
@Test
public void whenMeasureResponseTime_thenOK() {
Response response = RestAssured.get("/users/eugenp");
long timeInMS = response.time();
long timeInS = response.timeIn(TimeUnit.SECONDS);
assertEquals(timeInS, timeInMS/1000);
}
注意:
- time() 方法获取的响应时间的单位是毫秒
- timeln() 获取的响应时间的单位需要显示指定
8.1 判断响应时间
我们还可以借助简单的长匹配器验证响应时间(以毫秒为单位):
@Test
public void whenValidateResponseTime_thenSuccess() {
when().get("/users/eugenp").then().time(lessThan(5000L));
}
如果想使用不同的单位验证响应时间,那么我们将使用 time() 方法以及 TimeUnit 参数指定时间单位:
@Test
public void whenValidateResponseTimeInSeconds_thenSuccess(){
when().get("/users/eugenp").then().time(lessThan(5L),TimeUnit.SECONDS);
}
9. XML 格式响应断言
REST-assured 不仅可以断言 JSON 格式响应,还可以断言 XML 格式响应。
假设一个请求 http://localhost:8080/employees 的响应内容为如下 XML 格式:
<employees>
<employee category="skilled">
<first-name>Jane</first-name>
<last-name>Daisy</last-name>
<sex>f</sex>
</employee>
</employees>
我们可以通过如下代码断言 first-name 的值是否为 Jane:
@Test
public void givenUrl_whenXmlResponseValueTestsEqual_thenCorrect() {
post("/employees").then().assertThat()
.body("employees.employee.first-name", equalTo("Jane"));
}
我们还可以通过将主体匹配器验证所有值是否与预期值匹配,如下所示:
@Test
public void givenUrl_whenMultipleXmlValuesTestEqual_thenCorrect() {
post("/employees").then().assertThat()
.body("employees.employee.first-name", equalTo("Jane"))
.body("employees.employee.last-name", equalTo("Daisy"))
.body("employees.employee.sex", equalTo("f"));
}
或者使用带变量参数的方式:
@Test
public void givenUrl_whenMultipleXmlValuesTestEqualInShortHand_thenCorrect() {
post("/employees")
.then().assertThat().body("employees.employee.first-name",
equalTo("Jane"),"employees.employee.last-name",
equalTo("Daisy"), "employees.employee.sex",
equalTo("f"));
}
10. XPath 断言 XML
我们还可以使用 XPath 断言 XML 响应。如下例子就是断言响应中的 fist-name 属性的属性值:
@Test
public void givenUrl_whenValidatesXmlUsingXpath_thenCorrect() {
post("/employees").then().assertThat().
body(hasXPath("/employees/employee/first-name", containsString("Ja")));
}
XPath 还可以使用另一种类似 equalTo 匹配器的方法:
@Test
public void givenUrl_whenValidatesXmlUsingXpath2_thenCorrect() {
post("/employees").then().assertThat()
.body(hasXPath("/employees/employee/first-name[text()='Jane']"));
}
11. 记录测试详细信息
11.1 记录请求详情
首先来看如何使用 log().all() 来记录整个请求的日志:
@Test
public void whenLogRequest_thenOK() {
given().log().all()
.when().get("/users/eugenp")
.then().statusCode(200);
}
执行上述代码,将会输出如下日志内容:
Request method: GET
Request URI: https://api.github.com:443/users/eugenp
Proxy: <none>
Request params: <none>
Query params: <none>
Form params: <none>
Path params: <none>
Multiparts: <none>
Headers: Accept=*/*
Cookies: <none>
Body: <none>
只记录请求的特定部分,可以 log() 方法与 params()、body()、headers() 和 cookies()、method()、path() 结合使用,例如 log().params()。
请注意,使用的其他库或过滤器可能会改变实际发送到服务器的内容,因此这只应用于记录初始请求规范。
11.2 记录响应详情
同样地,我们也可以记录响应详情。
如下例子中我们只记录了响应体的日志详情:
@Test
public void whenLogResponse_thenOK() {
when().get("/repos/eugenp/tutorials")
.then().log().body().statusCode(200);
}
输出内容为:
{
"id": 9754983,
"name": "tutorials",
"full_name": "eugenp/tutorials",
"private": false,
"html_url": "https://github.com/eugenp/tutorials",
"description": "The \"REST With Spring\" Course: ",
"fork": false,
"size": 72371,
"license": {
"key": "mit",
"name": "MIT License",
"spdx_id": "MIT",
"url": "https://api.github.com/licenses/mit"
},
...
}
11.3 记录响应条件触发
我们还可以选择仅在发生错误或状态代码与给定值匹配时记录响应:
@Test
public void whenLogResponseIfErrorOccurred_thenSuccess() {
when().get("/users/eugenp")
.then().log().ifError();
when().get("/users/eugenp")
.then().log().ifStatusCodeIsEqualTo(500);
when().get("/users/eugenp")
.then().log().ifStatusCodeMatches(greaterThan(200));
}
11.4 记录断言失败
只有在验证失败时,我们才能记录请求和响应:
@Test
public void whenLogOnlyIfValidationFailed_thenSuccess() {
when().get("/users/eugenp")
.then().log().ifValidationFails().statusCode(200);
given().log().ifValidationFails()
.when().get("/users/eugenp")
.then().statusCode(200);
}
在本例中,我们希望验证状态代码是否为200。只有当失败时,才会记录请求和响应。
12. 总结
在本文中,我们了解了 REST-assured 框架,并查看了它最重要的特性,我们可以使用这些特性来测试RESTful 服务并验证它们的响应。
所有这些示例和代码片段的可以在 REST-assured 的 GitHub 中找到。