Rest-Assured



Rest-Assured 介紹

什么是 Rest-Assured ?

Rest-Assured 是一套由 Java 實現的輕量級的 REST API 測試框架,可以直接編寫代碼向服務器端發起 HTTP 請求,並驗證返回結果。

看看官方是怎么說的:

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.

與動態語言(如 Ruby 或 Groovy)相比,使用 Java 測試和驗證 REST 服務要困難得多。
RestAssured 則將這些語言的簡單性帶入了 Java 域。

優點

  • 簡約的接口測試 DSL(Domain Specific Language,即領域特定語言。DSL 是高效簡潔的領域語言,與通用語言相比能極大降級理解和使用難度,同時極大提高開發效率的語言),因此代碼足夠簡潔,編寫測試用例快。
  • 順序控制結構(最簡單的結構莫過於此,執行這條語句然后執行下一條語句的形式)。
  • 符合契約編程思想(如果前置條件滿足的情況下調用函數,那么函數的執行將確立后置條件)。
  • 支持 XML、JSON 的結構化解析。
  • 支持 Xpath、Jsonpath、Gpath 等多種解析方式。
  • 對 Spring 的支持較為全面。

image


什么是 REST API ?

REST API 就是符合 REST 的風格,要了解 REST API 首先需要弄清楚 REST 風格的具體含義。REST 的全稱是 Representational State Transfer,中文是表述性狀態轉移。這個是什么意思等會再解釋,先說明為什么會出現 REST,以及它對整個網絡服務 API 發展的重要性。

在互聯網服務剛剛起步的時候,網頁大部分是靜態的,用戶與服務器之間的交互比較少,但是隨着動態網頁的發展,操作的種類也越來越繁復,接口冗余且復雜,開發難度大大提升,擴展困難,而且網絡 traffic 的負擔也增大了。

針對這些問題,2000 年一篇論文提出了 REST 這個設計理念。REST 的宗旨是從資源的角度來觀察整個網絡,分布在各處的資源由 URI 確定,而客戶端的應用通過 URI 來獲取資源的表征。REST 與平台、語言等均無關,但是目前僅有 HTTP 是 REST 的實現案例。

通常,RESTful 服務應具有以下屬性和功能

  • Representations
  • Messages
  • URIs
  • Uniform interface
  • Stateless
  • Links between resources
  • Caching

1)Representations(表述性)

  • RESTful 服務的重點是資源以及如何提供對這些資源的訪問。資源可以很容易地被視為 OOP 中的對象,資源也可以包含其他資源。在設計系統時,首先要確定資源並確定它們之間的相互關系。這類似於設計數據庫的第一步:識別實體和關系。

  • 一旦我們確定了我們的資源,我們需要的下一件事就是找到一種在我們的系統中表示這些資源的方法。我們可以使用任何格式來表示資源,因為 REST 不會對表示的格式施加限制。所以,千萬不要以為 REST 風格的設計,就一定采用 JSON 進行數據交換,在 REST 中也可以使用 XML 存儲和數據交換,這個沒有強制規定。只不過,我們在工作中,看到的設計,絕大部分都是采用 JSON 格式來進行數據交換。

2)Messages(消息)

  • 客戶端和服務通過消息相互通信。 客戶端向服務器發送請求,服務器回復響應。除實際數據外,這些消息還包含有關消息的一些元數據。

  • 簡單來說,一個消息包含以下部分:
    image

3)URI(統一資源標識符)

4)Uniform interface(統一接口)

  • 統一的接口就是,不管是瀏覽器還是移動 app,都是同一個接口請求。主要是 GET、POST、PUT、DELETE 這幾個方法。

5)Stateless(無狀態)

  • RESTful 服務是無狀態的,不會為任何客戶端維護應用程序狀態。請求不能依賴於過去的請求,並且服務會獨立地處理每個請求。這個無狀態通信是 REST 的一個設計原則。服務器除了當前請求之外,它不知道客戶端什么狀態,不會紀錄和本次請求之外的其他數據。

6)Links between resources(資源之間的聯系)

  • 資源表示可以包含指向其他資源的鏈接,例如 HTML 頁面包含指向其他頁面的鏈接。服務返回的表示應該像網站一樣驅動流程。當我們訪問任何網站時,我們將看到一個索引頁面。 單擊其中一個鏈接並移至另一個頁面,依此類推。所以,在設計之前,考慮好對象之間的關系很重要。

7)Caching(高速緩存)

  • 緩存是存儲生成的結果並使用存儲的結果的概念,而不是在相同的請求在不久的將來到達時重復生成它們。這可以在客戶端、服務器或它們之間的任何其他組件上完成,例如代理服務器。緩存是提高服務性能的好方法,但如果管理不當,可能會導致客戶端被提供過時的結果。

  • 緩存是可以通過 HTTP 中頭部的 Cache-Control 這個字段來控制。


Rest-assured 快速入門

Rest-assured 接口測試步驟:

  1. 創建 maven 項目;
  2. 添加 Rest-assured、Junit 依賴;
  3. 編寫用例;
  4. 添加斷言;
  5. 調試。

1)Maven 依賴

        <!-- 引入 rest-assured 基礎依賴 -->
        <dependency>
            <groupId>io.rest-assured</groupId>
            <artifactId>rest-assured</artifactId>
            <version>3.0.0</version>
            <scope>test</scope>
        </dependency>
        <!-- json、xml 類的報文需要另外引入依賴 -->
        <dependency>
            <groupId>io.rest-assured</groupId>
            <artifactId>json-schema-validator</artifactId>
            <version>4.1.2</version>
            <scope>test</scope>
        </dependency>
        <!-- 引入 junit5 -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.8.2</version>
            <scope>test</scope>
        </dependency>

2)測試代碼

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

import java.util.HashMap;

import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.hasItems;

public class DemoTest {

    // 簡單的get無參請求
    @Test
    public void testGet() {
        // url:http://www.baidu.com
        // then():斷言動作
        // statusCode(200):斷言響應狀態碼是否等於200
        given().get("http://www.baidu.com").then().statusCode(200);
    }

    // 打印log
    @Test
    public void testGetHtml(){
        // log():打印日志信息
        // log().all():打印所有日志信息
        // 前后的log()分別表示請求和響應的日志信息
        given().log().all().get("http://www.baidu.com").then().log().all().statusCode(200);
    }

    // rest-assured 最期望的格式
    @Test
    public void testMp3() {
        given().  // 一次網絡請求所需要的條件都寫在這里,如頭信息、query參數
                queryParam("wd", "mp3").
        when().  // 觸發條件
                get("http://www.baidu.com/s").
        then().  // 斷言動作
                log().all().
                statusCode(200);
    }

    // post 請求
    @Test
    public void testPostJson() {
        HashMap<String, Object> map = new HashMap<>();
        map.put("username", "admin");
        map.put("password", "admin");
        given().log().all()
                contentType(ContentType.JSON).  // 設置JSON格式
                body(map).
        when().
                post("http://localhost:8080/renren-fast/sys/login").
        then().
                statusCode(200).
                // 可有多個斷言
                body("user.uername", equalTo("admin")).  // 是否等於
                // 斷言該報文中價格低於10的書籍title包含"Moby Dick"和"Sayings of the Century"這兩個關鍵字
                body("store.book.findAll { it.price < 10 }.title",hasItems("Sayings of the Century", "Moby Dick"));
    }
}

指定請求數據

param(請求參數)

通常我們可以這樣指定參數:

given().
       param("param1", "value1").
       param("param2", "value2").
when().
       get("/something");

REST Assured 將自動嘗試基於 HTTP 方法確定哪個參數類型(即查詢或表單參數):

  • 在 GET 的情況下,param 自動作為查詢參數;
  • 在 POST 的情況下,param 自動作為表單參數。

在某些情況下,還需要在 PUT 或 POST 中區分表單查詢參數。比如可以這樣使用:

given().
       formParam("formParamName", "value1").
       queryParam("queryParamName", "value2").
when().
       post("/something");

參數也可以在 url 上進行配置:

..when().get("/name?firstName=John&lastName=Doe");

多值參數:每個參數名稱具有多於一個值的參數(即每個名稱的值的列表),可以使用 var-args 指定這些值:

given().param("myList", "value1", "value2"). .. 

或者使用 list 列表:

List<String> values = new ArrayList<String>();
values.add("value1");
values.add("value2");

given().param("myList", values). .. 

無值參數:還可以指定一個沒有值的請求或表單參數

given().param("paramName"). ..

路徑參數:還可以在請求中指定所謂的路徑參數

post("/reserve/{hotelId}/{roomNumber}", "My Hotel", 23);

這些種類的路徑參數在 REST Assured 中稱為“未命名路徑參數”,因為它們是基於索引的(hotelId 將等於 "My Hotel",因為它是第一個占位符)。

我們還可以使用命名路徑參數:

given().
        pathParam("hotelId", "My Hotel").
        pathParam("roomNumber", 23).
when(). 
        post("/reserve/{hotelId}/{roomNumber}").
then().
        ..

路徑參數使得更容易讀取請求路徑,且使請求路徑能夠在具有不同參數值的許多測試中容易地重復使用。

從版本 2.8.0 開始,還可以混合未賦值和賦值好的路徑參數:

given().
        pathParam("hotelId", "My Hotel").        
when(). 
        post("/reserve/{hotelId}/{roomNumber}", 23).  // roomNumber的值"My Hotel"將被替換為23
then().
        ..

注意,指定太少或太多的參數容易導致錯誤消息。


通常模式下,我們可以通過以下方法指定 Cookie:

given().cookie("username", "John").when().get("/cookie").then().body(equalTo("username"));

也可以像這樣給 cookie 指定多個值:

given().cookie("cookieName", "value1", "value2"). ..
// 這將創建兩個 cookie:cookieName = value1 和 cookieName = value2

還可以使用以下方式指定詳細的 Cookie:

Cookie someCookie = new Cookie.Builder("some_cookie", "some_value").setSecured(true).setComment("some comment").build();
given().cookie(someCookie).when().get("/cookie").then().assertThat().body(equalTo("x"));

或同時指定 cookies:

Cookie cookie1 = Cookie.Builder("username", "John").setComment("comment 1").build();
Cookie cookie2 = Cookie.Builder("token", 1234).setComment("comment 2").build();
Cookies cookies = new Cookies(cookie1, cookie2);
given().cookies(cookies).when().get("/cookie").then().body(equalTo("username, token"));

Header(請求頭)

given().header("MyHeader", "Something").and(). ..
given().headers("MyHeader", "Something", "MyOtherHeader", "SomethingElse").and(). ..

也可以給一個 headers 指定多個值:

given().header("headerName", "value1", "value2"). ..
// 這將創建兩個 header,headerName = value1 和 headerName = value2

Header 合並/覆蓋

默認情況下,header 合並可以這樣:

given().header("x", "1").header("x", "2"). ..
// 請求將包含兩個標頭,"x:1" 和 "x:2"。

我們可以在 HeaderConfig 的基礎上進行更改。例如:

given().
        config(RestAssuredConfig.config().headerConfig(headerConfig().overwriteHeadersWithName("x"))).
        header("x", "1").
        header("x", "2").
when().
        get("/something").
...
// 這意味着只有 header "x = 2" 被發送到服務器

Body(請求體)

given().body("some body"). .. // Works for POST, PUT and DELETE requests
given().request().body("some body"). .. // More explicit (optional)
given().body(new byte[]{42}). .. // Works for POST, PUT and DELETE
given().request().body(new byte[]{42}). .. // More explicit (optional)

還可以將 Java 對象序列化為 JSON 或 XML 。


Multi-part(表單數據)

通常我們在向服務器傳輸大容量的數據時(比如文件)會使用 multi-part 表單數據技術。而 rest-assured 提供了 multiPart 方法來辨別這究竟是文件、二進制序列、輸入流還是上傳的文本。

例如,表單中上傳一個文件可以這樣:

given().
        multiPart(new File("/path/to/file")).
when().
        post("/upload");

它將會假設有一個 control 叫做“file”,在 HTML 中這意味着 input 標簽的屬性值為 file。為了解釋得更清楚,請看下面的 HTML 表單:

<form id="uploadForm" action="/upload" method="post" enctype="multipart/form-data">
        <input type="file" name="file" size="40">
        <input type=submit value="Upload!">
</form>

在這個例子中,control 的名字就是一個屬性名為 file 的 input 標簽。如果我們使用的 control 名不是這個,需要指定:

given().
        multiPart("controlName", new File("/path/to/file")).
when().
        post("/upload");

在同一個請求中提供多個 multi-part 事務也是可能的:

byte[] someData = ..
given().
        multiPart("controlName1", new File("/path/to/file")).
        multiPart("controlName2", "my_file_name.txt", someData).
        multiPart("controlName3", someJavaObject, "application/json").
when().
        post("/upload");

還可以通過使用 MultiPartConfig 指定默認的 control 名和文件名。例如:

given().config(config().multiPartConfig(multiPartConfig().defaultControlName("something-else"))). ..
// 這就會默認把 control 名配置為 "something-else" 而不是 "file"

RequestSpecification(請求對象)

Rest-Assured 充分利用了 Java 多態的特性,對接口進行了高度的繼承和封裝。查看 get 或者 post 等一系列 http 請求方法的實現,你會發現對於所有的請求體 Request,Rest-Assured 本身都對他進行了重新定義,即 RequestSpecification,這只是一個接口,它的實現類則是 TestSpecificationImpl,這里面則是封裝了標准的 http 請求,它是使用 groovy 語言進行實現的。

Groovy 是一種基於 JVM(Java 虛擬機)的敏捷開發語言,它結合了 Python、Ruby 和 Smalltalk 的許多強大的特性,且能夠與 Java 代碼很好地結合,也能用於擴展現有代碼。

代碼示例

private String url;
private String method = "get";
private String contentType;
private String body;
private HashMap<String, String> query;
private HashMap<String, String> headers;
private Response response;
	
// ... 屬性賦值
	
RequestSpecification requestSpecification = given().log().all();

requestSpecification.contentType(contentType);
requestSpecification.headers(headers);
requestSpecification.formParams(query);
requestSpecification.body(body);

response = requestSpecification.request(method, url).then().log().all().extract().response();

獲取響應數據

獲取響應體數據

比方說我們想通過發起一個 get 請求 "/lotto" 並獲取其響應內容。可以有多種方式:

InputStream stream = get("/lotto").asInputStream(); // Don't forget to close this one when you're done
byte[] byteArray = get("/lotto").asByteArray();
String json = get("/lotto").asString();

extract

我們可以從響應信息中提取值,或者使用 extract 方法返回 response 的一個實例。如果我們想獲取響應里的值,並將其作為接下來的請求內容,這會很有用。

下面是一個叫做 title 的資源返回的 JSON 數據:

{
    "title" : "My Title",
        "_links": {
            "self": {"href": "/title"},
            "next": {"href": "/title?page=2"}
        }
}

示例 1:想驗證內容類型是 JSON 格式且標題是 "My Title",且還想要從中提取 "next" 的中的 "href" 的值並用來發起請求

// 提取"href"的值
String nextTitleLink =
given().
        get("/title").
then().
        contentType(JSON).
        body("title", equalTo("My Title")).
extract().
        path("_links.next.href");

// 發起請求
get(nextTitleLink). ..

示例 2:如果我們想提取多個值,也可以考慮返回整個響應體

Response response = 
given().
        get("/title").
then().
        contentType(JSON).
        body("title", equalTo("My Title")).
extract().
        response();

String nextTitleLink = response.path("_links.next.href");
String headerValue = response.header("headerName");

JsonPath

一旦取得了響應體,還可以使用 JsonPath 來提取相應的數據。

注意這里的 JsonPath 是基於 Groovy 的 GPath,不要和 Jayway 的搞混了。

假設 http://localhost:8080/store 返回如下的 JSON:

{  
   "store":{  
      "book":[  
         {  
            "author":"Nigel Rees",
            "category":"reference",
            "price":8.95,
            "title":"Sayings of the Century"
         },
         {  
            "author":"Evelyn Waugh",
            "category":"fiction",
            "price":12.99,
            "title":"Sword of Honour"
         },
         {  
            "author":"Herman Melville",
            "category":"fiction",
            "isbn":"0-553-21311-3",
            "price":8.99,
            "title":"Moby Dick"
         },
         {  
            "author":"J. R. R. Tolkien",
            "category":"fiction",
            "isbn":"0-395-19395-8",
            "price":22.99,
            "title":"The Lord of the Rings"
         }
      ]
   }
}

例 1:搜集滿足 price 字段值小於 10 的所有 book 數組里的 title 字段,並斷言是否得到 "Sayings of the Century" 和 "Moby Dick" 這兩個關鍵字。

given().
       get("/store").
then().
       body("store.book.findAll { it.price < 10 }.title", hasItems("Sayings of the Century", "Moby Dick"));

而如果使用 JsonPath,則可以用下面的方法替代:

import static io.restassured.RestAssured.get;
import static io.restassured.path.json.JsonPath.from;

// Get the response body as a String
String response = get("/store").asString();
// And get all books with price < 10 from the response. "from" is statically imported from the JsonPath class
List<String> bookTitles = from(response).getList("store.book.findAll { it.price < 10 }.title");

或者更高效:

JsonPath jsonPath = new JsonPath(response);
List<String> bookTitles = jsonPath.get("store.book.findAll { it.price < 10 }.title");

注意這里是獨立地使用了 JsonPath,而沒有依賴 rest-assured 本身的功能。


JsonPath 配置

我們可以為 JsonPath 配置反序列化對象(object de-serializers),示例如下:

JsonPath jsonPath = new JsonPath(SOME_JSON).using(new JsonPathConfig("UTF-8"));

也可以靜態配置好 JsonPath,這樣所有的 JsonPath 實例都會共享這個配置:

JsonPath.config = new JsonPathConfig("UTF-8");

獲取某個路徑下的值

如我們只是想發起一個請求並返回一個路徑下的值,我們可以使用一個捷徑:

int lottoId = get("/lotto").path("lotto.lottoid");

rest-assured 會基於響應體的 content-type 自動決定是使用 JsonPath 還是 XmlPath。如果這個類型在 rest-assured 沒有被定義,它將會自動到 default parser 中查找。我們也可以自行(代碼指定)決定使用哪種,比如:

String firstName = post("/greetXML?firstName=John&lastName=Doe").andReturn().xmlPath().getString("firstName");

xmlPath、jsonPath 和 htmlPath 都是可選項。


獲取頭信息、cookie、響應狀態碼

import static io.restassured.RestAssured.get;

Response response = get("/lotto");

// 獲取所有 headers 信息
Headers allHeaders = response.getHeaders();

// 獲取單個 header 信息
String headerName = response.getHeader("headerName");

// 獲取所有 cookie 鍵值對
Map<String, String> allCookies = response.getCookies();

// 獲取單個 cookie 信息
String cookieValue = response.getCookie("cookieName");

// 獲取狀態行信息
String statusLine = response.getStatusLine();

// 獲取狀態碼信息
int statusCode = response.getStatusCode();

多個 header:可以使用 Headers.getValues() 方法返回一個具有所有 header 值的 List 列表。

多個 cookie:可以使用 Cookie.getValues() 方法獲取所有值,該方法返回包含所有 Cookie 值的 List 列表。


詳細的 Cookies 信息

如果我們需要獲取 Cookie 的路徑或過期日期等詳細信息,可以使用 Response.getDetailedCookie(java.lang.String) 方法獲取單個 Cookie,包括與給定名稱相關聯的所有屬性。

還可以使用 Response.getDetailedCookies() 方法獲取詳細的響應 cookies。


斷言

Body

統計

示例:如何斷言所有 author 字段值長度總和是否大於 50 的結果?

本例正展示了閉包和 Groovy 集合的強大之處。在 rest-assured 里可以:

given().
       get("/store").
then().
       body("store.book.author.collect { it.length() }.sum()", greaterThan(50));
  1. 首先我們通過 (store.book.author) 得到了所有的 author 字段值,然后使用閉包里的方法 { it.length() } 解析這個集合。

  2. 它所做的是對列表里的每一個 author 字段執行一次 length() 方法,然后返回一個新的列表。在這個列表中,我們再調用 sum() 方法來求得字符長度的總和。

  3. 最終的結果是 53,我們使用 greaterThan 匹配器的斷言結果是大於 50 。

若使用 JsonPath 來獲取這個結果:

import static io.restassured.RestAssured.get;
import static io.restassured.path.json.JsonPath.from;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;

// Get the response body as a string
String response = get("/store").asString();
// Get the sum of all author length's as an int. "from" is again statically imported from the JsonPath class
int sumOfAllAuthorLengths = from(response).getInt("store.book.author*.length().sum()");
// We can also assert that the sum is equal to 53 as expected.
assertThat(sumOfAllAuthorLengths, is(53));

全匹配

get("/x").then().assertThat().body(equalTo("something")). ..

關聯類型驗證

我們可以使用響應中的數據來驗證響應的另一部分。例如,從服務端返回的以下 JSON:

{ "userId": "some-id", "href": "http://localhost:8080/some-id" }

我們可能會注意到,"href" 屬性以 "userId" 屬性的值結尾。 如果我們想驗證這個,我們可以實現一個 io.restassured.matcher.ResponseAwareMatcher,如下:

get("/x").then().body("href", new ResponseAwareMatcher<Response>() {
                                  public Matcher<?> matcher(Response response) {
                                          return equalTo("http://localhost:8080/" + response.path("userId"));
                                  }
                       });

如果我們使用 Java 8,還可以使用 lambda 表達式:

get("/x").then().body("href", response -> equalTo("http://localhost:8080/" + response.path("userId"));

匹配器

有一些預定義的匹配器,我們可以使用在 io.restassured.matcher.RestAssuredMatchers(或 io.restassured.module.mockmvc.matcher.RestAssuredMockMvcMatchers,即 spring-mock-mvc 模塊)中定義。例如:

get("/x").then().body("href", endsWithPath("userId"));

ResponseAwareMatchers 也可以與另一個 ResponseAwareMatcher 或與 Hamcrest Matcher 組成。 例如:

get("/x").then().body("href", and(startsWith("http:/localhost:8080/"), endsWithPath("userId")));

and 方法是由 io.restassured.matcher.ResponseAwareMatcherComposer 靜態導入的。


get("/x").then().assertThat().cookie("cookieName", "cookieValue"). ..
get("/x").then().assertThat().cookies("cookieName1", "cookieValue1", "cookieName2", "cookieValue2"). ..
get("/x").then().assertThat().cookies("cookieName1", "cookieValue1", "cookieName2", containsString("Value2")). ..

狀態碼

get("/x").then().assertThat().statusCode(200). ..
get("/x").then().assertThat().statusLine("something"). ..
get("/x").then().assertThat().statusLine(containsString("some")). ..

get("/x").then().assertThat().header("headerName", "headerValue"). ..
get("/x").then().assertThat().headers("headerName1", "headerValue1", "headerName2", "headerValue2"). ..
get("/x").then().assertThat().headers("headerName1", "headerValue1", "headerName2", containsString("Value2")). ..

還可以在驗證頭時使用映射函數。例如要驗證 "Content-Length" 頭部小於 1000,則可以使用映射函數首先將頭值轉換為 int,然后在使用 Hamcrest 驗證前使用 “整數” 匹配器:

get("/something").then().assertThat().header("Content-Length", Integer::parseInt, lessThan(1000));

Content-Type

get("/x").then().assertThat().contentType(ContentType.JSON). ..

對象映射

rest-assured 支持從 JSON 和 XML 中映射 Java 對象。映射 JSON 需要 classpath 中有 Jackson 或者 Gson 才能使用,XML 則需要 JAXB。

序列化

假設我們有下面的 Java 對象:

public class Message {
    private String message;

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

我們需要將這個對象序列化為 JSON 並發送到請求中。可以有多種方式:

1)基於 Content-Type 的序列化

Message message = new Message();
message.setMessage("My messagee");
given().
       contentType("application/json").
       body(message).
when().
      post("/message");

在這個例子里,由於請求中的 content-type 被設置為 "application/json",rest-assured 也就會把對象序列化為 JSON。rest-assured 首先會在我們的 classpath 中尋找 Jackson,如果沒有則使用 Gson。如果我們把請求中的 content-type 修改為"application/xml",rest-assured 將會使用 JAXB 把對象序列化為 XML。如果沒有指定 content-type,rest-assured 會按照以下的優先級進行序列化:

  • 使用 Jackson 2 將對象序列化為 JSON(Faster Jackson (databind))
  • 使用 Jackson 將對象序列化為 JSON(databind)
  • 使用 Gson 將對象序列化為 JSON
  • 使用 JAXB 將對象序列化為 XML

rest-assured 也關心 content-type 的字符集(charset)等:

Message message = new Message();
message.setMessage("My messagee");
given().
       contentType("application/json; charset=UTF-16").
       body(message).
when().
      post("/message");

我們也可以把 Message 這個實例序列化為一個表單參數:

Message message = new Message();
message.setMessage("My messagee");
given().
       contentType("application/json; charset=UTF-16").
       formParam("param1", message).
when().
      post("/message");
// 這個 message 對象將會被實例化為 utf-16 編碼的 JSON(如果有 Jackson 或者 Gson)

2)由 HashMap 創建 JSON

我們也可以提供一個 Map,給 rest-assured 創建一個 JSON。如下:

Map<String, Object>  jsonAsMap = new HashMap<>();
jsonAsMap.put("firstName", "John");
jsonAsMap.put("lastName", "Doe");

given().
        contentType(ContentType.JSON).
        body(jsonAsMap).
when().
        post("/somewhere").
then().
        statusCode(200);

這將會產生一個 JSON 數據(JSON payload):

{ "firstName" : "John", "lastName" : "Doe" }

3)使用顯式序列化器


如果我們的 classpath 中同時有多個對象、或者不考慮 content-type 的設置,可以顯示地指定一個序列化器。

Message message = new Message();
message.setMessage("My messagee");
given().
       body(message, ObjectMapperType.JAXB).
when().
      post("/message");

在這個例子中 message 對象將會被 JAXB 序列化為一個 XML。


反序列化

再次假設我們有以下的 Java 對象:

public class Message {
    private String message;

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

我們需要把響應體反序列化為一個 Message 對象。

1)基於 Content-Type 的反序列化

假設服務端返回一個這樣的 JSON:

{"message":"My message"}

如下將它反序列化為一個 Message 對象:

Message message = get("/message").as(Message.class);

注意響應體的 content-type 必須是 "application/json"(或者其它包含 "json" 的類型)。如果服務端返回:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<message>
      <message>My message</message>
</message>

且 content-type 是"application/xml",代碼可以完全不用修改:

Message message = get("/message").as(Message.class);

2)使用顯式反序列化器

如果我們的 classpath 下同時有多個對象或者不在意響應體的 content-type,我們可以使用顯示的反序列化器。

Message message = get("/message").as(Message.class, ObjectMapperType.GSON);

日志

在大量的用例中,打印出響應或者請求的細節將有助於創建正確的預期、發送准確的請求。為此我們可以使用 rest-assured 預定義的過濾器,或者使用其中的快捷方法。

請求日志

given().log().all(). .. // Log all request specification details including parameters, headers and body
given().log().params(). .. // Log only the parameters of the request
given().log().body(). .. // Log only the request body
given().log().headers(). .. // Log only the request headers
given().log().cookies(). .. // Log only the request cookies
given().log().method(). .. // Log only the request method
given().log().path(). .. // Log only the request path

響應日志

如果我們想打印除了狀態碼以外的響應信息,可以:

get("/x").then().log().body() ..

這樣做,無論是否有異常錯誤發生,都會打印出響應信息。如果我們希望只有當錯誤發生時才打印響應信息,可以:

get("/x").then().log().ifError(). .. 

我們也可以記錄響應里包括狀態碼、header、cookie 的所有細節:

get("/x").then().log().all(). .. 

也可以只記錄狀態碼、header 或者 cookie:

get("/x").then().log().statusLine(). .. // Only log the status line
get("/x").then().log().headers(). .. // Only log the response headers
get("/x").then().log().cookies(). .. // Only log the response cookies

我們也可以配置為僅當狀態碼匹配某個值時才打印響應體:

get("/x").then().log().ifStatusCodeIsEqualTo(302). .. // Only log if the status code is equal to 302
get("/x").then().log().ifStatusCodeMatches(matcher). .. // Only log if the status code matches the supplied Hamcrest matcher

獲取響應時長

從 REST Assured 2.8.0 開始支持測量響應時間,例如:

long timeInMs = get("/lotto").time()

或使用特定時間單位:

long timeInSeconds = get("/lotto").timeIn(SECONDS);

其中 SECONDS 只是一個標准的 TimeUnit。還可以使用 DSL 驗證:

when().
      get("/lotto").
then().
      time(lessThan(2000L)); // Milliseconds

或:

when().
      get("/lotto").
then().
      time(lessThan(2L), SECONDS);

需要注意的是,只能參考性地將這些測量數據與服務器請求處理時間相關聯(因為響應時間將包括 HTTP 往返和 REST Assured 處理時間等,不能做到十分准確)。


路徑配置

根路徑

為避免在 body 方法里使用重復的路徑,我們可以指定一個根路徑:

when().
         get("/something").
then().
         body("x.y.firstName", is(..)).
         body("x.y.lastName", is(..)).
         body("x.y.age", is(..)).
         body("x.y.gender", is(..));

使用根路徑則如下:

when().
        get("/something").
then().
         root("x.y"). // You can also use the "root" method
         body("firstName", is(..)).
         body("lastName", is(..)).
         body("age", is(..)).
         body("gender", is(..));

也可以設置一個默認的根路徑,對所有的 RestAssured 實例生效:

RestAssured.rootPath = "x.y";

路徑參數

在預定義的路徑包含變量時,路徑參數會很有用。路徑參數遵循 Java 的標准格式語法。

示例如下:

import static io.restassured.RestAssured.withArgs;

String someSubPath = "else";
int index = 1;
get("/x").then().body("something.%s[%d]", withArgs(someSubPath, index), equalTo("some value")). ..

以上示例會對 "something.else[0]" 是否等於 "some value" 進行斷言。

另一種用法是針對復雜的根路徑:

when().
       get("/x").
then().
       root("filters.filterConfig[%d].filterConfigGroups.find { it.name == 'GroupName' }.includes").
       body(withArgs(0), hasItem("first")).
       body(withArgs(1), hasItem("second")).
       ..

有時當所有在根路徑中指定的參數都已經驗證過了,只想要驗證一個不含多余參數的 body 時,則可以使用 withNoArgs

when().
         get("/jsonStore").
then().
         root("store.%s", withArgs("book")).
         body("category.size()", equalTo(4)).
         appendRoot("%s.%s", withArgs("author", "size()")).
         body(withNoArgs(), equalTo(4));

在許多高級用例中,在根路徑上附加一些參數也很有用。如我們可以使用 appendRoot 方法:

when().
         get("/jsonStore").
then().
         root("store.%s", withArgs("book")).
         body("category.size()", equalTo(4)).
         appendRoot("%s.%s", withArgs("author", "size()")).
         body(withNoArgs(), equalTo(4));

也可以對根路徑進行拆分:

when().
         get("/jsonStore").
then().
         root("store.category").
         body("size()", equalTo(4)).
         detachRoot("category").
         body("size()", equalTo(1));

Session 支持

原生使用

Rest-Assured 提供了一套簡單的管理 Session 的方式。我們可以在 DSL(領域特定語言)中預定義一個 session 的 id 值:

// 方式一(簡寫)
given().sessionId("1234"). .. 
// 方式二
given().cookie("JSESSIONID", "1234"). ..

我們也可以為所有的請求指定一個默認的 sessionId:

RestAssured.sessionId = "1234";

默認情況下 session id 的名字是 JSESSIONID,但是我們可以通過使用 SessionConfig 來修改:

RestAssured.config = RestAssured.config().sessionConfig(new SessionConfig().sessionIdName("phpsessionid"));

我們也可以指定一個 sessionid 並在其它用例中復用,使用 RequestSpecBuilder:

RequestSpecBuilder spec = new RequestSpecBuilder().setSessionId("value1").build();

// Make the first request with session id equal to value1
given().spec(spec). .. 
// Make the second request with session id equal to value1
given().spec(spec). .. 

從響應對象中獲取一個 session id:

String sessionId = get("/something").sessionId();

Session 過濾器

2.0.0 版本起,我們可以使用 session filter 截獲並提供一個 session,舉個例子:

SessionFilter sessionFilter = new SessionFilter();

given().
          auth().form("John", "Doe").
          filter(sessionFilter).
when().
          get("/formAuth").
then().
          statusCode(200);


given().
          filter(sessionFilter). // Reuse the same session filter instance to automatically apply the session id from the previous response
when().
          get("/x").
then().
          statusCode(200);

要想獲取由 SessionFilter 截獲的 session id:

String sessionId = sessionFilter.getSessionId();


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM