Rest-Assured介绍

image-20210104161120141

rest-assured官方文档

rest-assured使用文档

rest-assured中文使用参考文档

官方简单介绍

1
Testing and validating REST services in Java is harder than in dynamic languages such as Ruby and Groovy. REST Assured brings the simplicity of using these languages into the Java domain. 

在 REST Assured 的官方 GitHub 上有这样一句简短的描述: Java DSL for easy testing of REST services 简约的 REST 服务测试 Java DSL

Rest-Assured是一个测试RESTful Web Services的Java类库。可以使用Rest-Assured编写高度自定义化的HTTP请求用来测试各种各样Restful服务组合的业务实现。

Rest-Assured同样能够验证从服务器返回的HTTP响应报文,例如服务器响应状态码,响应报文内容等,Rest-Assured可以灵活的用来进行Restful Webservice测试。

用Java做接口自动化测试首选REST Assured,具体原因如下:

  • 开源

  • 简约的接口测试DSL

  • 支持xml json的结构化解析

  • 支持xpath jsonpath gpath等多种解析方式

  • 对spring的支持比较全面

Rest-Assured使用

基本三步曲

接口进行测试一般由三步曲

  • 传参
  • 发请求
  • 响应结果断言

REST Assured给我们提供了清晰的三步曲,以given、when、then的结构来实现,基本写法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//使用参数
given().
param("key1", "value1").
param("key2", "value2").
when().
post("/somewhere").
then().
body(containsString("OK"))

//使用X-Path (XML only)
given().
params("firstName", "John", "lastName", "Doe").
when().
post("/greetMe").
then().
body(hasXPath("/greeting/firstName[text()='John']"))

分步解析

分步拆解一:Given

发送请求经常需要带有参数,使用given()就可以实现,如下很多传参方法如下:

image-20210104160116438

在传参的方法中包含了 parampathParamqueryParam formParam,下面来研究下这几个传参方法的区别

  • param

通常我们都会使用 given().param 方法来传参,REST Assured 会根据 HTTP 方法自动尝试确定哪种参数类型(即查询或表单参数),如果是 GET,则查询参数将自动使用,如果使用 POST,则将使用表单参数;

  • queryParamformParam

有时候在 PUT 或 POST 请求中,需要区分查询参数和表单参数时,就需要使用queryParam 和 formParam 方法了,具体写法如下:

1
2
3
4
5
given().
formParam("formParamName", "value1").
queryParam("queryParamName", "value2").
when().
post("/something")
  • pathParam

使用given时指定请求路径的参数,这个方法很少用到,具体写法如下:

1
2
3
4
5
6
7
given().
pathParam("OAuth", "oauth").
pathParam("accessToken", "token").
when().
post("/auth/{OAuth}/{accessToken}").
then().
..
  • header/headers

经常还需要在请求头中带入参数,这个时候就可以使用headerheaders方法,写法如下:

1
2
3
given()
.header("Authorization","Basic c3lzdGVtOxxxbQ==")
.header("Host","xxx.xxx.xxx.xxx")

或者用headers将多个参数写在一起:

1
2
given()
.headers("Authorization","Basic c3lzdGVtxxx3RlbQ==","Host","47.xxx.xxx.133")
  • cookie

有时候需要在请求中带入cookierestassured提供了cookie方法来实现:

1
2
3
given()
.cookie("c_a","aaaaaa")
.cookie("c_b","bbbbbb"). ..
  • contentType

经常还会设置contentType,最常见的就是application/json了,写法如下:

1
2
3
given().contentType("application/json"). ..
//或者
given().contentType(ContentType.JSON). ..
  • body

在POST, PUT 或 DELETE请求中,我们经常还需要带上请求体body,写法如下:

1
2
3
4
5
6
7
given().body("{\n" +
"\t\"password\": \"elcrD28xxxR0VLs/jERA\\u003d\\u003d\\n\",\n" +
"\t\"grant_type\": \"password\",\n" +
"\t\"scope\": \"server\",\n" +
"\t\"userType\": 1,\n" +
"\t\"username\": \"xxx\"\n" +
"}")

也可以用request更为明确的指出是请求body

1
2
3
4
5
6
7
given().request().body("{\n" +
"\t\"password\": \"elcrD28xxxR0VLs/jERA\\u003d\\u003d\\n\",\n" +
"\t\"grant_type\": \"password\",\n" +
"\t\"scope\": \"server\",\n" +
"\t\"userType\": 1,\n" +
"\t\"username\": \"xxx\"\n" +
"}")
  • 没有参数

如果我们没有参数需要传递,也可以省略掉**given()**:

1
get("/lotto").then().assertThat().body("lotto.lottoId", equalTo(5));
  • proxy

有时候我们需要进行接口的调试,抓包是最常用的一种方式,rest-assured 提供了 proxy 方法,可以设置代理,写法如下:

1
given().proxy("127.0.0.1",8888). ..

image-20210104162443312

image-20210104162516564

分步拆解二:When

  • when主要用来触发请求,在when后面接着请求URL:
1
given().when().post("http://xx.xxx.xxx.xx/auth/oauth/token"). ..
  • 前面在given中我们设置了很多请求参数,在when中也可以设置,只不过要注意的是在请求之前设置;这也比较好理解,如果再请求之后的话,参数都设置怎么发请求呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
given()
.when()
.contentType(ContentType.JSON)
.headers("Authorization","Basic c3lzxxx3RlbQ==","Host","47.xxx.xxx.133")
.request().body("{\n" +
"\t\"password\": \"elcrD28ZSLLtR0VLs/jERA\\u003d\\u003d\\n\",\n" +
"\t\"grant_type\": \"password\",\n" +
"\t\"scope\": \"server\",\n" +
"\t\"userType\": 1,\n" +
"\t\"username\": \"qinzhen\"\n" +
"}")
.post("http://xx.xxx.xxx.xx/auth/oauth/token")
. ..

分步拆解二:Then

then后面可以跟断言,也可以获取响应值

  • 断言-then().body()

then().body() 可以对响应结果进行断言,在 body 中写入断言:

1
2
.. post("http://xx.xxx.xxx.xx/auth/oauth/token")
.then().statusCode(200).body("code",equalTo(1));

其中statusCode(200)是对状态码的断言,判断状态码是否为200;body(“code”,equalTo(1))是对返回体中的code进行断言,要求返回code值为1

注意:这里的equalTo使用的是hamcrest断言

  • 获取响应-then().extract().body().path(“code”)

我们可以在then后面利用.extract().body() 来获取我们想要body的返回值,它们也可以直接接在断言后面,写法如下:

1
2
3
.. .then()
.log().all().statusCode(200).body("code",equalTo(1))
.extract().body().path("code");

注意:这里的body() 不要和请求体body()以及断言的body()混淆了

接口鉴权

鉴权–API的安全问题,在请求时都需要带上鉴权认证,Rest-Assured支持多种鉴权方式(可查看官网),下面就介绍常见的几种鉴权方式

  • oauth2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    /**
    * 查询GitHub的repo信息--oauth2
    * auth().oauth2() 该方式是将token信息隐式在请求体重
    * auth().preemptive().oauth2() 显示将鉴权信息在header中携带
    * 直接指定header参数进行鉴权 header("Authorization", "token xxxxxx")
    */
    @Test
    void queryRepoOauth() {
    given().log().all()
    //.auth().oauth2("token")
    //.auth().preemptive().oauth2("token")
    .header("Authorization", "token token")
    .when()
    .get("https://api.github.com/user/repos")
    .then()
    .log().all();
    }
  • basic

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    /**
    * 查询GitHub的repo信息--basic
    * basic("username", "password") 中的username和password替换为自己的GitHub的用户名和密码即可
    */
    @Test
    void queryRepoBasic() {
    given().log().all()
    .auth().preemptive().basic("username", "password")
    .when()
    .get("https://api.github.com/user/repos")
    .then()
    .log().all();
    }

请求处理实战

  1. 创建Maven项目,并引入依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <dependency>
    <groupId>io.rest-assured</groupId>
    <artifactId>rest-assured</artifactId>
    <version>${rest-assured.version}</version>
    </dependency>

    <dependency>
    <groupId>io.rest-assured</groupId>
    <artifactId>json-path</artifactId>
    <version>${rest-assured.version}</version>
    </dependency>

    <dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>RELEASE</version>
    <scope>test</scope>
    </dependency>

  1. RESTFul接口实例接口为例,针对于RESTful的接口,进行请求处理

  1. 请求实战

    • Get请求
    • POST请求
    • PUT请求
    • DELETE请求
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    package com.api.demo;

    import io.restassured.RestAssured;
    import io.restassured.http.ContentType;
    import org.junit.jupiter.api.BeforeAll;
    import org.junit.jupiter.api.DisplayName;
    import org.junit.jupiter.api.Test;

    import static io.restassured.RestAssured.given;

    /**
    * Rest-Assured请求处理,发送不同的Request
    *
    * @author jingLv
    * @date 2021/01/04
    */
    public class TestApi {

    private static String token;
    private static final Integer USER_ID = 1;

    @BeforeAll
    static void setUp() {
    RestAssured.baseURI = "http://localhost:8886/v1";
    String loginBody = "{\n" +
    " \"username\":\"xiaohong\",\n" +
    " \"password\":\"123123\"\n" +
    "}";
    token = given()
    .when()
    .contentType("application/json")
    .body(loginBody)
    .get("/user/login")
    .then()
    .log().body()
    .extract().response().path("data.token");
    }

    /**
    * 发送post请求 -- 创建用户信息
    */
    @Test
    @DisplayName("创建用户信息")
    void addUserInfo() {
    String userInfoBody = "{\n" +
    " \"userId\":" + USER_ID + ",\n" +
    " \"userName\":\"小红\",\n" +
    " \"email\":\"xiaohong@qq.com\",\n" +
    " \"phone\":\"18623456543\",\n" +
    " \"friends\":[\n" +
    " {\n" +
    " \"userId\":11,\n" +
    " \"userName\":\"小红喵\",\n" +
    " \"email\":\"xiaohongmiao@qq.com\",\n" +
    " \"phone\":\"18623456555\"\n" +
    " },\n" +
    " {\n" +
    " \"userId\":12,\n" +
    " \"userName\":\"小红旺\",\n" +
    " \"email\":\"xiaohongwang@qq.com\",\n" +
    " \"phone\":\"18623456566\"\n" +
    " }\n" +
    " ]\n" +
    "}";
    given()
    .log().all()
    .contentType(ContentType.JSON)
    .header("token", token)
    .body(userInfoBody)
    .when()
    .post("/info/user")
    .then()
    .log()
    .status().statusCode(200);
    }

    /**
    * 发送get请求 -- 查询所有用户信息
    */
    @Test
    @DisplayName("查询所有用户信息")
    void findAllUserInfo() {
    given()
    .log().all()
    .contentType(ContentType.JSON)
    .header("token", token)
    .when()
    .post("/info/user")
    .then()
    .log()
    .status().statusCode(200);
    }

    /**
    * 发送put请求 -- 更新指定用户的部分信息
    */
    @Test
    @DisplayName("更新指定用户的部分信息")
    void updateUserInfoByUserId() {
    String updateUserInfoById = "{\n" +
    " \"userId\":" + USER_ID + ",\n" +
    " \"userName\":\"小红红\",\n" +
    " \"email\":\"xiaohong红@qq.com\",\n" +
    " \"phone\":\"18623456543\"\n" +
    "}";
    given()
    .log().all()
    .contentType(ContentType.JSON)
    .header("token", token)
    .body(updateUserInfoById)
    .when()
    .put("/info/user/" + USER_ID)
    .then()
    .log()
    .status().statusCode(200);
    }

    /**
    * 发送delete请求 -- 删除指定用户信息
    */
    @Test
    @DisplayName("删除指定用户信息")
    void deleteUserInfoByUserId() {
    given()
    .log().all()
    .contentType(ContentType.JSON)
    .header("token", token)
    .when()
    .delete("/info/user/" + USER_ID)
    .then()
    .log()
    .status().statusCode(200);
    }
    }