REST-assured Framework 指北

简介: REST-assured Framework 指北

1. REST-assured 介绍

REST assured 框架被设计用来简化 REST API 的测试和验证,并受 Ruby 和 Groovy 等动态语言中使用的测试框架的较大影响。

REST-assured 有着对 HTTP 的强大的支持,支持 HTTP 的基本的请求方式,除此之外还有一些其他的特性。

在本指南中,我们将认识 REST-assured,并将使用 Hamcrest 进行断言。如果你还不熟悉Hamcrest,可以先看一下这篇文章:使用 Hamcrest 测试

此外,要了解 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, CONNECTOPTIONS

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


相关文章
|
3月前
|
自然语言处理 前端开发 测试技术
Playwright初学指南 (2):全面解析元素定位策略
本文深入解析Playwright革命性的元素定位体系,详解八大核心定位策略(语义化角色、文本内容、标签属性等)及其适用场景,提供动态元素处理方案和调试技巧。通过定位策略性能对比和企业级最佳实践,帮助开发者构建健壮、可维护的自动化测试脚本,有效解决75%的Web自动化测试失败问题。
|
人工智能 JavaScript 前端开发
毕业设计|基于Spring Boot和Vue.js的前后端分离商城系统(二)
毕业设计|基于Spring Boot和Vue.js的前后端分离商城系统
182 1
|
IDE 网络协议 安全
阿里Java编程规约【九】 注释规约
1.【强制】类、类属性、类方法的注释必须使用 Javadoc 规范,使用 /** 内容 */ 格式,不得使用 // xxx 方式。 说明:在 IDE 编辑窗口中,Javadoc 方式会提示相关注释,生成 Javadoc 可以正确输出相应注释;在 IDE 中,工程调用方法时,不进入方法即可悬浮提示方法、参数、返回值的意义,提高阅读效率。
2054 0
|
网络安全
Caused by: org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://xxxx.svc.cluster.local:8080/xxxx": Connection reset; nested exception is java.net.SocketException: Connection reset 什么原因导致得
Caused by: org.springframework.web.client.ResourceAccessException: I/O error on POST request for "xxxx.svc.cluster.local:8080/xxxx ": Connection reset; nested exception is java.net.SocketException: Connection reset 什么原因导致得
4837 0
Java代码解释++i和i++的五个主要区别
本文介绍了前缀递增(++i)和后缀递增(i++)的区别。两者在独立语句中无差异,但在赋值表达式中,i++ 返回原值,++i 返回新值;在复杂表达式中计算顺序不同;在循环中虽结果相同但使用方式有别。最后通过 `Counter` 类模拟了两者的内部实现原理。
Java代码解释++i和i++的五个主要区别
|
前端开发 JavaScript
CSS样式穿透技巧:利用scoped与deep实现前端组件样式隔离与穿透
CSS样式穿透技巧:利用scoped与deep实现前端组件样式隔离与穿透
1520 1
|
前端开发 JavaScript API
探索Python Django中的WebSocket集成:为前后端分离应用添加实时通信功能
【7月更文挑战第17天】现代Web开发趋势中,前后端分离配合WebSocket满足实时通信需求。Django Channels扩展了Django,支持WebSocket连接和异步功能。通过安装Channels、配置设置、定义路由和消费者,能在Django中实现WebSocket交互。前端使用WebSocket API连接后端,实现双向数据流,如在线聊天功能。集成Channels提升Web应用的实时性和用户体验,适应实时交互场景的需求。**
566 6
|
JSON Java API
使用 REST-assured 获取并断言响应数据
使用 REST-assured 获取并断言响应数据
|
消息中间件 网络协议 物联网
MQTT常见问题之MQTT突然连不上如何解决
MQTT(Message Queuing Telemetry Transport)是一个轻量级的、基于发布/订阅模式的消息协议,广泛用于物联网(IoT)中设备间的通信。以下是MQTT使用过程中可能遇到的一些常见问题及其答案的汇总:
|
Java Maven
【亮剑】Java项目开发中常遇到Jar 包依赖冲突问题,主要由不同版本库、循环依赖、传递依赖和依赖范围不当引起
【4月更文挑战第30天】Java项目开发中常遇到依赖冲突问题,主要由不同版本库、循环依赖、传递依赖和依赖范围不当引起。解决冲突需分析依赖树、定位冲突源、调整类加载顺序等。方法包括排除冲突依赖、统一管理版本、限定依赖范围、合并冲突类、升级降级库版本及拆分模块。关注依赖关系,及时解决冲突,保障项目稳定运行。
1339 0