1. 简介

REST-assured 是一个专门用于简化 REST API 测试与验证的 Java 库,其设计深受 Ruby 和 Groovy 等动态语言测试技术的影响。

该库对 HTTP 协议提供了强大支持,不仅覆盖了基础动词和标准操作,还提供了大量高级功能。

本文将深入探索 REST-assured 的核心能力,并结合 Hamcrest 进行断言验证。如果你还不熟悉 Hamcrest,建议先阅读这篇教程:Hamcrest 测试指南

想要了解更多高级用法?推荐阅读以下文章:

现在让我们从一个简单示例开始。

2. 基础示例测试

开始前,确保你的测试类包含以下静态导入:

io.restassured.RestAssured.*
io.restassured.matcher.RestAssuredMatchers.*
org.hamcrest.Matchers.*

这些导入能简化测试代码,方便访问核心 API。

下面是一个基础示例:假设我们有一个博彩系统 API,返回比赛数据:

{
    "id": "390",
    "data": {
        "leagueId": 35,
        "homeTeam": "Norway",
        "visitingTeam": "England",
    },
    "odds": [{
        "price": "1.30",
        "name": "1"
    },
    {
        "price": "5.25",
        "name": "X"
    }]
}

当访问 http://localhost:8080/events?id=390 时,会返回上述 JSON。现在用 REST-assured 验证关键字段:

@Test
public void givenUrl_whenSuccessOnGetsResponseAndJsonHasRequiredKV_thenCorrect() {
   get("/events?id=390").then().statusCode(200).assertThat()
      .body("data.leagueId", equalTo(35)); 
}

这段代码验证了接口 /events?id=390 返回的 JSON 中,data.leagueId 的值为 35。

再看个复杂点的例子:验证 odds 数组是否包含价格 1.305.25

@Test
public void givenUrl_whenJsonResponseHasArrayWithGivenValuesUnderKey_thenCorrect() {
   get("/events?id=390").then().assertThat()
      .body("odds.price", hasItems("1.30", "5.25"));
}

3. 环境搭建

使用 Maven 时,在 pom.xml 添加 rest-assured 依赖

<dependency>
    <groupId>io.rest-assured</groupId>
    <artifactId>rest-assured</artifactId>
    <version>5.5.0</version>
    <scope>test</scope>
</dependency>

REST-assured 依赖 Hamcrest 进行断言,所以还需添加 hamcrest 依赖

<dependency>
    <groupId>org.hamcrest</groupId>
    <artifactId>hamcrest</artifactId>
    <version>2.1</version>
</dependency>

4. 匿名 JSON 根节点验证

考虑一个只包含基本类型的数组:

[1, 2, 3]

这称为匿名 JSON 根节点(无键值对),但仍是合法 JSON。验证这类数据时,使用 $ 或空字符串 "" 作为路径。假设接口 http://localhost:8080/json 返回上述数据:

when().get("/json").then().body("$", hasItems(1, 2, 3));

以下写法效果相同:

when().get("/json").then().body("", hasItems(1, 2, 3));

5. 浮点数处理

使用 REST-assured 时要注意:JSON 中的浮点数会被映射为 Java 的 float 类型,不能直接与 double 比较。看这个例子:

{
    "odd": {
        "price": "1.30",
        "ck": 12.2,
        "name": "1"
    }
}

如果这样测试 ck 的值:

get("/odd").then().assertThat().body("odd.ck", equalTo(12.2));

测试会失败,因为 equalTo(12.2)double 类型。正确做法是显式指定为 float

get("/odd").then().assertThat().body("odd.ck", equalTo(12.2f));

6. 指定请求方法

通常我们直接调用 get()post() 等方法发送请求。但也可以通过 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() 指定请求体。例如创建新赔率对象:

@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,也可直接传入字符串作为请求体。

7. 默认值配置

可以全局配置测试的默认值:

@BeforeEach
public void setup() {
    RestAssured.baseURI = "https://api.github.com";
    RestAssured.port = 443;
}

这里设置了基础 URI 和端口。此外还可配置:

  • 基础路径(basePath)
  • 根路径(root path)
  • 认证信息(authentication)

需要重置为默认值时调用:

RestAssured.reset();

8. 响应时间测量

使用 Response 对象的 time()timeIn() 方法测量响应时间:

@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():返回毫秒级响应时间
  • timeIn():返回指定时间单位的响应时间

8.1. 验证响应时间

通过 long 类型的 Matcher 验证响应时间(毫秒):

@Test
public void whenValidateResponseTime_thenSuccess() {
    when().get("/users/eugenp").then().time(lessThan(5000L));
}

如需按其他时间单位验证,使用带 TimeUnit 参数的 time() 方法:

@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 返回:

<employees>
    <employee category="skilled">
        <first-name>Jane</first-name>
        <last-name>Daisy</last-name>
        <sex>f</sex>
    </employee>
</employees>

验证 first-nameJane

@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. XML 的 XPath 支持

使用 XPath 验证 XML 响应。示例:验证 first-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>

如需记录特定部分,替换 all() 为:

  • params():参数
  • body():请求体
  • headers():请求头
  • cookies():Cookie
  • method():方法
  • path():路径
@Test
public void whenLogRequest_thenOK() { 
    given().log().params()
      .when().get("/users/eugenp")
      .then().statusCode(200);
}

注意:其他库或过滤器可能修改实际发送的内容,此方法仅记录初始请求规范。

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 服务并验证响应。通过这些技术,你可以:

  • 简化 API 测试流程
  • 灵活处理 JSON/XML 响应
  • 精确控制请求与响应验证
  • 高效调试问题(通过日志功能)

掌握这些技巧后,你的 API 测试效率将大幅提升,告别繁琐的手动验证。


原始标题:A Guide to REST-assured | Baeldung

« 上一篇: Spring Security表达式