配置功能Config

可以使用RestAssured内建的方法,进行全局的配置,有如下的方法:

1
2
3
4
5
RestAssured.baseURI = "https://api.github.com";
RestAssured.authentication = oauth2("token");
RestAssured.authentication = preemptive().basic("xxxx", "xxx");
RestAssured.port = 443;
RestAssured.basePath = "/user";

除了以上的配置,也可以使用RestAssured提供的配置功能进行实现,以下查看RestAssured提供的丰富的配置功能:

image-20210104181155823

我们已经看到RestAssured提供丰富的配置实现,以下我们看下两个具体配置实体

  1. 使用headerConfig重写的方法,对header参数进行覆盖处理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    /**
    * Header配置实例
    */
    @Test
    void headerConfig() {
    // 使用headerConfig重写的方法,对test的header参数进行覆盖处理
    RestAssured.config = RestAssured.config().headerConfig(HeaderConfig.headerConfig().overwriteHeadersWithName("test"));
    String loginBody = "{\n" +
    " \"username\":\"xiaohong\",\n" +
    " \"password\":\"123123\"\n" +
    "}";
    given()
    .log().all()
    .contentType(ContentType.JSON)
    .header("test", "aaa")
    .header("test", "bbb")
    .body(loginBody)
    .when()
    .get("/user/login")
    .then()
    .log()
    .body().statusCode(200);
    }
  1. 使用logConfig的enablePrettyPrinting设置为false,响应返回body则不会进行格式化显示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    /**
    * Log配置实例
    */
    @Test
    void logConfig() {
    // 使用logConfig的enablePrettyPrinting设置为false,响应返回body则不会进行格式化显示
    RestAssured.config = RestAssured.config().logConfig(LogConfig.logConfig().enablePrettyPrinting(false));
    String loginBody = "{\n" +
    " \"username\":\"xiaohong\",\n" +
    " \"password\":\"123123\"\n" +
    "}";
    given()
    .log().all()
    .contentType(ContentType.JSON)

    .body(loginBody)
    .when()
    .get("/user/login")
    .then()
    .log()
    .body().statusCode(200);
    }

模板功能Specification

我们在写接口测试的时候,在很多情况下我们会针对某些特定的接口写出大量的测试用例,而这些接口的请求和响应判断当中往往会有重复的内容定义,除了使用之前定义的通用配置,也可以使用RestAssured提供的模板功能来定义接口的请求和响应,在多个测试用例中使用,减少重复的测试代码编写。

image-20210104183515725

常用的就是RequestSpecification和ResponseSpecification。

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
package com.api.demo;

import io.restassured.RestAssured;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.builder.ResponseSpecBuilder;
import io.restassured.http.ContentType;
import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import static io.restassured.RestAssured.given;

/**
* @author jingLv
* @date 2021/01/04
*/
public class TestSpecification {

private static String token;
private static RequestSpecification requestSpecification;
private static ResponseSpecification responseSpecification;

@BeforeAll
static void setUp() {
RestAssured.baseURI = "http://localhost:8886/v1";
// 请求模板构造类,构造请求实例
RequestSpecBuilder requestSpecBuilder = new RequestSpecBuilder();
requestSpecBuilder.setContentType(ContentType.JSON);
requestSpecification = requestSpecBuilder.build();

// 响应模板构造类,构造响应实例
ResponseSpecBuilder responseSpecBuilder = new ResponseSpecBuilder();
responseSpecBuilder.expectStatusCode(200);
responseSpecification = responseSpecBuilder.build();
}

/**
* 使用模板功能来简化测试用例编写
*/
@Test
void testLogin() {
String loginBody = "{\n" +
" \"username\":\"xiaohong\",\n" +
" \"password\":\"123123\"\n" +
"}";
given()
.log().all()
.spec(requestSpecification)
.body(loginBody)
.when()
.get("/user/login")
.then()
.spec(responseSpecification)
.log();
}
}

过滤功能Filter

请求与响应内容的修改

过滤功能是指在请求发出之前,或者响应接收到之前,能够通过Filter过滤器,可以改变请求或者响应的内容。特别是使用了大量的模板功能的时候,需要对个别的用例进行微调,这是就可以通过Filter进行实现。

以下示例进行Request与Response的使用Filter进行修改

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
package com.api.demo;

import io.restassured.RestAssured;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.builder.ResponseBuilder;
import io.restassured.builder.ResponseSpecBuilder;
import io.restassured.response.Response;
import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import static io.restassured.RestAssured.given;

/**
* @author jingLv
* @date 2021/01/04
*/
public class TestFilter {

private static String token;
private static RequestSpecification requestSpecification;
private static ResponseSpecification responseSpecification;

@BeforeAll
static void setUp() {
RestAssured.baseURI = "http://localhost:8886/v1";
String loginBody = "{\n" +
" \"username\":\"xiaohong\",\n" +
" \"password\":\"123123\"\n" +
"}";
// 请求模板构造类,构造请求实例
RequestSpecBuilder requestSpecBuilder = new RequestSpecBuilder();
requestSpecBuilder.setBody(loginBody);
requestSpecification = requestSpecBuilder.build();

// 响应模板构造类,构造响应实例
ResponseSpecBuilder responseSpecBuilder = new ResponseSpecBuilder();
responseSpecBuilder.expectStatusCode(200);
responseSpecification = responseSpecBuilder.build();
}

/**
* 利用Filter过滤器实现request请求的修改
*/
@Test
void testLoginRequest() {
given()
.filter((filterableRequestSpecification, filterableResponseSpecification, filterContext) -> {
String loginBody = "{\n" +
" \"username\":\"xiaohei\",\n" +
" \"password\":\"123123\"\n" +
"}";
// 修改ContentType的类型
filterableRequestSpecification.body(loginBody);
return filterContext.next(filterableRequestSpecification, filterableResponseSpecification);
})
.log().all()
.spec(requestSpecification)
.when()
.get("/user/login")
.then()
.spec(responseSpecification)
.log();
}

/**
* 利用Filter过滤器实现response请求的修改
*/
@Test
void testLoginResponse() {
Response response = given()
.log().all()
.spec(requestSpecification)
.when()
.get("/user/login");
Response newResponse = new ResponseBuilder().clone(response)
.setBody("这是一个修改的Response").build();
newResponse.then()
.spec(responseSpecification)
.log().all();
}
}

Filter机制

rest-assured的Filter

image-20210104190705204

过滤器会在请求实际发起之前侦测和改变该请求的内容,也可以在响应体实际返回之前拦截并改变。您可以将其理解为AOP中的around advice(译者注:可以自行搜索切片编程)。过滤器也可以用在认证scheme、session管理、日志中。创建一个过滤器需要实现io.restassured.filter.Filter接口。

rest-assured提供了几个过滤器:

  1. io.restassured.filter.log.RequestLoggingFilter: 可以打印出请求模式的细节。
  2. io.restassured.filter.log.ResponseLoggingFilter: 可以打印响应信息的细节如果响应体的状态码匹配given方法的参数。
  3. io.restassured.filter.log.ErrorLoggingFilter: 如果发生了异常(状态码在400和500之间),过滤器将会打印响应的内容。

利用filter机制实现自动解密

  • filter可以应用于所有全局请求
  • request处理
    • 记录所有的request数据
    • 自动填充token
  • response处理
    • 重新构建新的response
    • filter((req,res,ctx))->{//重新生成response}
    • new ResponseBuilder().clone(originalResponse)

修改request

  • 可以修改请求内容
    • 自动带上cookie
  • 通用的请求数据记录
    • 记录所有的请求和响应

接口加解密处理

以base64加解密过程为例

  • 原始内容 -> 加密内容
  • 加密内容 -> internet -> response -> client
  • client -> filter -> 解密内容
  • body正常断言

示例

  1. WireMock模拟一个接口,返回的主体为Base64加密

  2. Filter机制,编写测试代码

    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
    /**
    * 使用filter处理请求返回body加密的情况
    */
    @Test
    void testEncode() {
    given().baseUri("http://localhost:9090")
    .log().all()
    .filter((request, response, context) -> {
    // 发送原始请求,但是返回的Response不具备set方法,无法修改body
    Response resOrigin = context.next(request, response);
    // 解密过程
    String raw = Base64.decodeStr(resOrigin.body().asString());
    // 响应构造器,ResponseBuilder的作用主要是在Response的基础上建设出来一个新的可以修改的body对象(克隆出来一个新的)
    ResponseBuilder resBuilder = new ResponseBuilder().clone(resOrigin);
    //Response无法直接修改body,所有间接的通过ResponseBuilder构建
    resBuilder.setBody(raw);
    //ResponseBuilder在最后通过build方法直接创建一个用于返回的不可修改的Response
    return resBuilder.build();
    })
    .when()
    .get("/api/encryption").prettyPeek()
    .then()
    .log().all()
    .body("books.name[0]", equalTo("西游记"))
    .statusCode(200);
    }
  1. 执行结果

    image-20210107141917565

  2. 如果是所有接口都需要加解密处理呢?我们可以使用RestAssured配置功能,全局添加

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    /**
    * 全局抽出来的filter对象
    */
    @BeforeAll
    static void before() {
    RestAssured.filters((req, res, ctx) -> {
    // 添加过滤接口路径,包含就处理加解密过程,如果不包含就不进行处理
    if (req.getURI().contains("/api/encryption")) {
    // 返回的Response不具备set方法,无法修改body
    Response resOrigin = ctx.next(req, res);
    // 解密过程
    String raw = Base64.decodeStr(resOrigin.body().asString());
    // 响应构造器,ResponseBuilder的作用主要是在Response的基础上建设出来一个新的可以修改的body对象
    ResponseBuilder resBuilder = new ResponseBuilder().clone(resOrigin);
    //Response无法直接修改body,所有间接的通过ResponseBuilder构建
    resBuilder.setBody(raw);
    //ResponseBuilder在最后通过build方法直接创建一个用于返回的不可修改的Response
    return resBuilder.build();
    } else {
    return ctx.next(req, res);
    }
    }
    );
    }

Session Filter

  • sessionIdName
  • sessionId
  • session filter可以自动从请求中提取sessionId,并在以后的请求中再附带进cookie发送出去

示例

以Jenkins模拟登录,获取Session,请求其他的接口

image-20210107144844154

image-20210107145246523

根据获取到的信息,编写代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
void testJenkinsLogin() {
RestAssured.config = RestAssured.config().sessionConfig(
new SessionConfig().sessionIdName("JSESSIONID.7ef748a1"));
SessionFilter sessionFilter = new SessionFilter();

given().log().all()
.filter(sessionFilter)
.queryParam("j_password", "xxxx")
.queryParam("Submit", "登录")
.queryParam("j_username", "xxxx")
.when().post("http://xx.xxx.xxx.xx:8080/j_acegi_security_check")
.then()
.statusCode(302);

given().log().all().filter(sessionFilter)
.when().get("http://xx.xxx.xxx.xx:8080/job/xxxx/").prettyPeek()
.then().statusCode(200);
}