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 中找到。