REST-Assured指南

2023/05/09

1. 简介

Rest-Assured旨在简化REST API的测试和验证,并深受动态语言(如Ruby和Groovy)中使用的测试技术的影响。

该库对HTTP提供了可靠的支持,当然从动词和标准HTTP操作开始,但也远远超出了这些基础知识。

在本指南中,我们将探索Rest-Assured的基本使用,并使用Hamcrest进行断言。如果你还不熟悉Hamcrest,应该首先阅读这篇文章:使用Hamcrest进行测试

此外,要了解Rest-Assured的更高级用例,请查看我们的其他文章:

2. 简单测试示例

在开始之前,让我们确保我们的测试具有以下静态导入:

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对象的leagueId为35。

让我们看一个更有趣的例子。假设你想验证odds数组是否包含price为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匹配器的强大功能来执行其断言,因此我们还必须包含该依赖项:

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

此链接始终提供最新版本。

4. 匿名JSON根验证

考虑一个由基本类型而不是对象组成的数组:

[1, 2, 3]

这称为匿名JSON根,这意味着它没有键值对,但它仍然是有效的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类型的使用不能与double互换,Java中的许多场景都是如此。

一个典型的例子是以下这个响应:

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

假设我们对ck的值运行以下测试:

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

即使我们正在测试的值等于响应中的值,此测试也会失败。这是因为我们比较的是double而不是float。

为了让它正常通过,我们必须明确地将equalTo匹配器方法的操作数指定为float,如下所示:

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

6. 指定请求方式

通常,我们会通过调用与我们要使用的请求方法相对应的方法(例如get())来执行请求。

此外,我们还可以使用request()方法指定HTTP动词

@Test
public void whenRequestGet_thenOK(){
    when().request("GET", "/users/tuyucheng")
        .then()
        .statusCode(200);
}

上面的例子相当于直接使用get()。

类似地,我们可以发送HEAD、CONNECT和OPTIONS请求:

@Test
public void whenRequestHead_thenOK() {
    when().request("HEAD", "/users/tuyucheng")
        .then()
        .statusCode(200);
}

POST请求也遵循类似的语法,我们可以使用with()和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. 测量响应时间

让我们看看如何使用Response对象的time()和timeIn()方法测量响应时间

@Test
public void whenMeasureResponseTime_thenOK() {
    Response response = RestAssured.get("/users/tuyucheng");
    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/tuyucheng")
        .then()
        .time(lessThan(5000L));
}

如果我们想以不同的时间单位验证响应时间,那么我们可以使用带有第二个TimeUnit参数的time()匹配器:

@Test
public void whenValidateResponseTimeInSeconds_thenSuccess(){
    when().get("/users/tuyucheng")
        .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-name是Jane:

@Test
public void givenUrl_whenXmlResponseValueTestsEqual_thenCorrect() {
    post("/employees")
        .then()
        .assertThat()
        .body("employees.employee.first-name", equalTo("Jane"));
}

我们还可以通过将body匹配器链接在一起来验证所有值是否与我们的预期值匹配,如下所示:

@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验证我们的响应。考虑以下对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/tuyucheng7")
        .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/tuyucheng7/fullstack-tutorial4j")
        .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/tuyucheng7")
        .then().log().ifError();
    when().get("/users/tuyucheng7")
        .then().log().ifStatusCodeIsEqualTo(500);
    when().get("/users/tuyucheng7")
        .then().log().ifStatusCodeMatches(greaterThan(200));
}

11.4 验证失败时记录

我们也可以仅在验证失败时记录请求和响应:

@Test
public void whenLogOnlyIfValidationFailed_thenSuccess() {
    when().get("/users/tuyucheng7")
        .then().log().ifValidationFails().statusCode(200);

    given().log().ifValidationFails()
        .when().get("/users/tuyucheng")
        .then().statusCode(200);
}

在此示例中,我们要验证状态码是否为200。仅当此操作失败时,才会记录请求和响应。

12. 总结

在本教程中,我们探讨了Rest-Assured框架并演示了它最重要的功能,我们可以使用这些功能来测试我们的Restful服务并验证它们的响应。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章