一、靜態導入
為了有效的使用rest-assured,官網推薦從下列class中靜態導入方法:
1 io.restassured.RestAssured.* 2 io.restassured.matcher.RestAssuredMatchers.* 3 org.hamcrest.Matchers.*
如果想使用 Json Schema validation(驗證),還需要靜態導入下面的方法:
1 io.restassured.module.jsv.JsonSchemaValidator.*
如果我們正在使用的是 Spring MVC ,我們可以使用 spring--mock-mvc模型的rest-assured DSL來對Spring 的controllers層進行單元測試。為此我們需要從 RestAssuredMockMvc 中導入下面的方法,還不是從 io.restassured.RestAssured 導入:
1 io.restassured.module.mockmvc.RestAssuredMockMvc.*
二、使用實例
1.Json實例
假設 GET請求(to http://localhost:8080/lotto)返回下面的Json:
1 { 2 "lotto":{ 3 "lottoId":5, 4 "winning-numbers":[2,45,34,23,7,5,3], 5 "winners":[{ 6 "winnerId":23, 7 "numbers":[2,45,34,23,3,5] 8 },{ 9 "winnerId":54, 10 "numbers":[52,3,12,11,18,22] 11 }] 12 } 13 }
rest-assured可以非常簡單的發起這個GET請求並且驗證響應結果,比如:我們想驗證 lottoId 是否等於 5 ,可以這樣做:
1 get("/lotto").then().body("lotto.lottoId",equalTo(5));
或者我們想驗證 winnerId的值是否是23,54 :
1 get("/lotto").then().body("lotto.winners.winnerId",hasItems(23,54));
值得注意的是:equalTo()方法和 hasItems()方法是屬於 Hamcrest matchers 的方法,所有我們需要靜態導入 org.hamcrest.Matchers 。
注意:"json path" 使用 Groovy's GPath 標記法,不要與 Jayway's JsonPath 混淆。
2.以BigDecimal形式返回 floats 和 doubles
我們可以對rest-assured和JsonPath進行配置,使之以BigDecimal形式返回json里的數值類型數據,而不是返回 floats 和 doubles,參照下面的json:
1 { 2 3 "price":12.12 4 5 }
默認情況下,我們驗證 price的值是否等於12.12時是這樣做的:
1 get("/price").then().body("price", is(12.12f));
但是如果我們喜歡的話,我們可以通過JsonConfig 來配置rest-assured使之以BigDecimal形式返回json里的數值類型數據:
1 given(). 2 config(RestAssured.config().jsonConfig(jsonConfig().numberReturnType(BIG_DECIMAL))). 3 when(). 4 get("/price"). 5 then(). 6 body("price", is(new BigDecimal(12.12));
3.匿名JSON根節點驗證
一個json文本並不一定有一個命名好的根節點屬性,驗證這種類型的json,這里有一個例子:
1 [1, 2, 3]
一個匿名的json根路徑要驗證的話,可以使用 $ 或者是 空字符串 來驗證。例如,我們訪問 http://localhost:8080/json ,然后通過rest-assured來驗證:
1 //第一種方式,使用 $ 代替根節點 2 get("/json").then().body("$",hasItems(1,2,3)); 3 4 //第二種方式,使用 空字符串 代替根節點 5 get("/json").then().body("",hasItems(1,2,3));
4.XML實例
XML可以使用同樣的方式來驗證。假設向 http://localhost:8080/greetXML 發送一個POST請求,並且返回下面的xml:
1 <greeting> 2 <firstName>{params("firstName")}</firstName> 3 <lastName>{params("lastName")}</lastName> 4 </greeting>
上面的例子返回了一個基於 firstName 和 lastName 請求參數的greeting節點,我們可以通過rest-assured非常簡單展示和驗證,比如驗證 firstName :
1 given(). 2 parameters("firstName", "John", "lastName", "Doe"). 3 when(). 4 post("/greetXML"). 5 then(). 6 body("greeting.firstName", equalTo("John")).
如果我們想同時驗證 firstName 和 lastName ,我們可以這樣寫:
1 given(). 2 parameters("firstName", "John", "lastName", "Doe"). 3 when(). 4 post("/greetXML"). 5 then(). 6 body("greeting.firstName", equalTo("John")). 7 body("greeting.lastName", equalTo("Doe"));
或者是使用簡單的寫法:
1 with().parameters("firstName", "John", "lastName", "Doe").when().post("/greetXML").then().body("greeting.firstName", equalTo("John"), "greeting.lastName", equalTo("Doe"));
5.XML命名空間
為了使響應斷言把命名空間考慮在內,我們需要使用 io.restassured.config.XmlConfig 定義一個命名空間。例如,這里有一個叫做 namespace-example 的資源位於 http://localhost:8080,返回下面的XML:
1 <foo xmlns:ns="http://localhost/"> 2 <bar>sudo </bar> 3 <ns:bar>make me a sandwich!</ns:bar> 4 </foo>
我們可以申明 http://localhost:8080 這個uri並且驗證響應結果:
1 given(). 2 config(RestAssured.config().xmlConfig(xmlConfig().declareNamespace("test", "http://localhost/"))). 3 when(). 4 get("/namespace-example"). 5 then(). 6 body("foo.bar.text()", equalTo("sudo make me a sandwich!")). 7 body(":foo.:bar.text()", equalTo("sudo ")). 8 body("foo.test:bar.text()", equalTo("make me a sandwich!"));
這個路徑的語法遵循的是 Groovy's XmlSlurper 語法,需要注意的是一直到2.6.0版本,路徑語法都不支持 Groovy's XmlSlurper 語法。請看 release notes 查看2.6.0之前的語法。
6.XPath
我們也可以通過使用X-Path 來驗證XML響應結果,比如:
1 given().parameters("firstName", "John", "lastName", "Doe").when().post("/greetXML").then().body(hasXPath("/greeting/firstName", containsString("Jo")));
或者:
1 given().parameters("firstName", "John", "lastName", "Doe").post("/greetXML").then().body(hasXPath("/greeting/firstName[text()='John']"));
如果需要在XPath表達式里面使用命名空間的話,需要在配置中啟用這些選項:
1 given(). 2 config(RestAssured.config().xmlConfig(xmlConfig().with().namespaceAware(true))). 3 when(). 4 get("/package-db-xml"). 5 then(). 6 body(hasXPath("/db:package-database", namespaceContext));
namespaceContext
是一個javax.xml.namespace.NamespaceContext 的實例 。
三、高級使用實例(復雜的解析以及驗證)
這正是rest-assured的閃光點所在,因為rest-assured實現了Groovy,rest-assured從 Groovy 的API中獲得了很大的好處。讓我們先從 Groovy 的例子來看:
1 def words = ['ant', 'buffalo', 'cat', 'dinosaur'] 2 def wordsWithSizeGreaterThanFour = words.findAll { it.length() > 4 }
第一行代碼我們只是簡單定義了一個包含字符的list集合,第二行代碼非常的有趣。第二行代碼中我們通過Groovy閉包調用 findAll 方法來查詢list集合中所有長度大於4的字符。這個閉包中有一個叫 it 的內部變量,這個 it 就代表了list集合中的當前元素。這段代碼的結果是一個新的list集合:wordsWithSizeGreaterThanFour ,包含 buffalo
and dinosaur。
下面有一些有趣的方法,也可以在 Groovy 集合中使用:
- find ------ 查找第一個匹配閉包斷言(closure predicate)的元素
- collect ------封裝集合中的每一個元素調用閉包的返回值
- sum -------集合中所有元素之和
- max/min --------返回集合中的最大值或最小值
那么我們在驗證XML和Json時,如何應用這些優點呢???
1.XML例子
例如,我們有個資源 http://localhost:8080/shopping 返回下面的XML:
1 <shopping> 2 <category type="groceries"> 3 <item>Chocolate</item> 4 <item>Coffee</item> 5 </category> 6 <category type="supplies"> 7 <item>Paper</item> 8 <item quantity="4">Pens</item> 9 </category> 10 <category type="present"> 11 <item when="Aug 10">Kathryn's Birthday</item> 12 </category> 13 </shopping>
接下來我們要寫一個test方法來驗證 類型為 groceries 的category節點是否包含 Chocolate和Coffee兩個元素,在rest-assured中我們可以這么寫:
1 when(). 2 get("/shopping"). 3 then(). 4 body("shopping.category.find { it.@type == 'groceries' }.item", hasItems("Chocolate", "Coffee"));
那么上面的例子當中發生了什么呢?首先使用XPath方法 shopping.category 獲取所有的category的一個list集合,在這個list集合中我們調用 find 方法,返回type屬性值等於"groceries"的單個category節點,在這個category節點中我們繼續獲得了所有item元素。從上面可以看出category節點下不止一個item元素,所以會返回一個list集合,然后我們通過Hamcrest matcher 的 hasitems 方法來驗證這個list。這就是上面這個例子整個執行過程!
但是如果我們想獲得上面的item,然后又不想使用 Hamcrest matcher 的 hasitems 方法來驗證,那么我們可以使用XmlPath:
1 // 獲得response,並且以字符串輸出 2 String response = get("/shopping").asString(); 3 // 從response中獲得groceries,"from"是從XmlPath類中靜態導入的 4 List<String> groceries = from(response).getList("shopping.category.find { it.@type == 'groceries' }.item");
如果在response中我們僅僅關心的是 groceries ,我們還可以這樣做:
1 // 獲得response,並以字符串形式輸出 2 List<String> groceries = get("/shopping").path("shopping.category.find { it.@type == 'groceries' }.item");
1.1 深度優先搜索
事實上,前面的例子我們還可以進一步簡化一下:
1 when(). 2 get("/shopping"). 3 then(). 4 body("**.find { it.@type == 'groceries' }", hasItems("Chocolate", "Coffee"));
** 是一種在XML文件中做深度搜索的捷徑。我們搜索第一個具有type屬性值等於"groceries"的節點,注意我們並沒有在"item"這個Xml路徑結束,原因是在category節點返回item值的list集合時toString() 方法被自動調用了。
2.JSON例子
例如,我們有個資源 http://localhost:8080/store 返回下面的JSON:
1 { 2 "store":{ 3 "book":[ 4 { 5 "author":"Nigel Rees", 6 "category":"reference", 7 "price":8.95, 8 "title":"Sayings of the Century" 9 }, 10 { 11 "author":"Evelyn Waugh", 12 "category":"fiction", 13 "price":12.99, 14 "title":"Sword of Honour" 15 }, 16 { 17 "author":"Herman Melville", 18 "category":"fiction", 19 "isbn":"0-553-21311-3", 20 "price":8.99, 21 "title":"Moby Dick" 22 }, 23 { 24 "author":"J. R. R. Tolkien", 25 "category":"fiction", 26 "isbn":"0-395-19395-8", 27 "price":22.99, 28 "title":"The Lord of the Rings" 29 } 30 ] 31 } 32 }
例1,我們發起一個請求"/store",並且斷言:book的價格(price)小於10的title,是否包含"Sayings of the Century" 和 "Moby Dick"兩個元素:
1 get("/store").then().body("store.book.findAll{it.price<10}.title",hasItems("Sayings of the Century","Moby Dick"));
跟上面Xml的例子,我們首先使用閉包來查找所有符合價格(price)小於10的books,然后返回books的titles集合。接着我們使用 hasItems 方法來斷言我們期望獲得的titles。如果使用JsonPath返回這個titles,我們可以使用下面的方式來代替:
1 //以字符串形式輸出response 2 Response response = get("/store").asString(); 3 //從response中獲得所有price<10的book,"from"是從JsonPath類中靜態導入的 4 List<String> titles = from(response).getList("store.book.findAll{it.price<10}.title");
例2,我們來考慮下如果我們想要斷言:所有author字段值長度的總和是否大於50。這看起來是一個難以回答的問題,這也正顯示了閉包(closures)和Groovy集合的強大之處。在rest-assured中我們可以這樣做:
1 when(). 2 get("/store"). 3 then(). 4 body("store.book.author.collect{it.length()}.sum()",greaterThan(50));
首先我們通過 store.book.author 獲得了所有的authors字段值並返回一個authors集合,然后我們調用 collect 方法來封裝通過閉包的 it.length() 方法獲得的每個元素的長度值,it.length方法的作用是:對列表里的每一個author都會執行一次length()方法,使用collect方法后返回一個新的list集合。在這個新的list集合我們調用 sum() 方法求所有長度之和。上面和的結果為53,然后我們使用 greateThan() 方法斷言長度之和是否大於50。
事實上,上面的例子我們還可以做進一步的簡化,讓我們再來看一下 "words" 這個例子:
1 def words = ['ant', 'buffalo', 'cat', 'dinosaur']
Groovy 有一個非常方便的方式用來遍歷集合中的每一個元素,使用展開操作符( *. )來操作:
1 def words = ['ant', 'buffalo', 'cat', 'dinosaur'] 2 assert [3, 6, 3, 8] == words*.length()
Groovy 返回了一個新的包含words中每個元素長度值的集合。我們也可以在rest-assured中應用這個方法在author上:
1 when(). 2 get("/store"); 3 then(). 4 body("store.book.author*.length().sum()", greaterThan(50)).
當然,我們也可以使用JsonPath來獲得這個結果:
1 // 以字符串形式輸出response 2 String response = get("/store").asString(); 3 // 獲得author字段值長度的和, "from" 是從JsonPath類中靜態導入的 4 int sumOfAllAuthorLengths = from(response).getInt("store.book.author*.length().sum()"); 5 // 斷言 6 assertThat(sumOfAllAuthorLengths, is(53));