本帖內容摘抄自:https://testerhome.com/topics/7060,在此基礎上增加練習筆記(在看本帖之前可以先看原文檔)健盒。
注:本文中接口均是moco的接口改淑,請參考《Moco接口框架應用實戰(zhàn)》了解moco基本知識亮蛔。
REST Assured是一個可以簡化HTTP Builder頂層 基于REST服務的測試過程的Java DSL(針對某一領域痴施,具有受限表達性的一種計算機程序設計語言)。它支持發(fā)起POST,GET,PUT,DELETE,OPTIONS,PATCH和HEAD請求究流,并且可以用來驗證和校對這些請求的響應信息辣吃。
靜態(tài)導入方法:
pom.xml中要加入以下依賴:
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>json-schema-validator</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>spring-mock-mvc</artifactId>
<version>3.0.6</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
</dependency>
推薦大家從以下的類中靜態(tài)導入方法,以提高使用rest-assured的效率芬探。
import io.restassured.RestAssured.*;
import io.restassured.matcher.RestAssuredMatchers.*;
import org.hamcrest.Matchers.*;
如果您想使用Json Schema validation 還應該靜態(tài)導入這些方法:
import io.restassured.module.jsv.JsonSchemaValidator.*;
更多使用方法參閱 Json Schema Validation 神得。
如果您正在使用SpringMVC,你可以使用spring-mock-mvc 模型的Rest Assured DSL來對Spring的controller層進行單元測試偷仿。為此需要從RestAssuredMockMvc靜態(tài)導入這些方法:
import io.restassured.module.mockmvc.RestAssuredMockMvc.*;
示例
例一 - JSON
假設某個get請求 (to http://localhost:8889/lotto) 返回JSON如下:
{
"description":"返回json接口",
"request":{
"uri":"/lotto",
"method":"get"
},
"response":{
"json":{
"lotto":{
"lottoId":5,
"winning-numbers":[2,45,34,23,7,5,3],
"winners":[{
"winnerId":23,
"numbers":[2,45,34,23,3,5]
},{
"winnerId":54,
"numbers":[52,3,12,11,18,22]
}]
}
}
}
}
REST assured可以幫您輕松地進行get請求并對響應信息進行處理哩簿。舉個例子,如果想要判斷l(xiāng)ottoId的值是否等于5酝静,你可以這樣做:
在類中寫一個方法节榜,寫下如下代碼:
@Test
public void testone(){
given()
.when()
.get("http://localhost:8889/lotto")
.then()
.body("lotto.lottoId",equalTo(5));
}
注:lottoId必須是int類型,才能用equalTo(5)别智,如果是string類型宗苍,用equalTo("5")
又或許您想要檢查winnerId的取值包括23和54:
@Test
public void testtwo(){
given()
.when()
.get("http://localhost:8889/lotto")
.then()
.body("lotto.winners.winnerId",hasItems(23,54));
}
注意: equalTo 和 hasItems 是 Hamcrest matchers的方法,所以需要靜態(tài)導入 import static org.hamcrest.Matchers.*;
注意這里的"json path"語法使用的是Groovy的GPath標注法薄榛,不要和Jayway的JsonPath語法混淆讳窟。(暫時可以不用關注)
以BigDecimal返回float和double類型
(譯者注:Java在java.math包中提供的API類BigDecimal,用來對超過16位有效位的數(shù)進行精確的運算)
您可以對rest-assured和JsonPath進行配置敞恋,使之以BigDecimal返回json里的數(shù)值類型數(shù)據(jù)丽啡,而不是float或者double∮裁ǎ可以參考下面json文本:
{
"description":"以BigDecimal返回float和double類型",
"request":{
"uri":"/price",
"method":"get"
},
"response":{
"json":{
"price":12.12
}
}
}
默認情況下您驗證price字段是否等于float類型的12.12像這樣:
@Test
public void testthree(){
given()
.when()
.get("http://localhost:8889/price")
.then()
.body("price",is(12.12f));
}
但是如果想用rest-assured的JsonConfig來配置返回的所有的json數(shù)值都為BigDecimal類型:
@Test
public void testfour(){
given()
.config(RestAssured.config().jsonConfig(jsonConfig().numberReturnType(BIG_DECIMAL)))
.when()
.get("http://localhost:8889/price")
.then()
.body("price",is(new BigDecimal(12.12)));
}
因為上面moco的接口沒有定義price是BigDecimal類型补箍,所以test的接口代碼會報錯,實際測試中遇到返回值類型是BigDecimal的可以用這個代碼啸蜜。
JSON Schema validation
自從 2.1.0
版本rest-assured開始支持Json Schema validation. 舉個例子馏予,在classpath中放置以下的schema文件(譯者注:idea的話可以放在resources目錄下),products-schema.json:
{
"definitions": {},
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "http://example.com/root.json",
"type": "object",
"title": "The Root Schema",
"required": [
"lotto"
],
"properties": {
"lotto": {
"$id": "#/properties/lotto",
"type": "object",
"title": "The Lotto Schema",
"required": [
"lottoId",
"winning-numbers",
"winners"
],
"properties": {
"lottoId": {
"$id": "#/properties/lotto/properties/lottoId",
"type": "integer",
"title": "The Lottoid Schema",
"default": 0,
"examples": [
5
]
},
"winning-numbers": {
"$id": "#/properties/lotto/properties/winning-numbers",
"type": "array",
"title": "The Winning-numbers Schema",
"items": {
"$id": "#/properties/lotto/properties/winning-numbers/items",
"type": "integer",
"title": "The Items Schema",
"default": 0,
"examples": [
2,
45,
34,
23,
7,
5,
3
]
}
},
"winners": {
"$id": "#/properties/lotto/properties/winners",
"type": "array",
"title": "The Winners Schema",
"items": {
"$id": "#/properties/lotto/properties/winners/items",
"type": "object",
"title": "The Items Schema",
"required": [
"winnerId",
"numbers"
],
"properties": {
"winnerId": {
"$id": "#/properties/lotto/properties/winners/items/properties/winnerId",
"type": "integer",
"title": "The Winnerid Schema",
"default": 0,
"examples": [
23
]
},
"numbers": {
"$id": "#/properties/lotto/properties/winners/items/properties/numbers",
"type": "array",
"title": "The Numbers Schema",
"items": {
"$id": "#/properties/lotto/properties/winners/items/properties/numbers/items",
"type": "integer",
"title": "The Items Schema",
"default": 0,
"examples": [
2,
45,
34,
23,
3,
5
]
}
}
}
}
}
}
}
}
}
您可以使用這個schema驗證(/products)這個請求是否符合規(guī)范:
@Test
public void testfive(){
given()
.when()
.get("http://localhost:8889/lotto")
.then()
.assertThat()
.body(matchesJsonSchemaInClasspath("products-schema.json"));
}
注:jsonschema 可以在https://www.jsonschema.net/網(wǎng)頁中生成盔性,
在resource包下創(chuàng)建products-schema.json,放入從網(wǎng)頁中生成的jsonschema呢岗。
matchesJsonSchemaInClasspath 靜態(tài)導入自 io.restassured.module.jsv.JsonSchemaValidator 并且我們推薦從這個類中靜態(tài)導入所有的方法冕香。
import static io.restassured.module.jsv.JsonSchemaValidator.matchesJsonSchemaInClasspath;
maven依賴蛹尝,上面已經(jīng)講過要加入該依賴:
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>json-schema-validator</artifactId>
<version>3.0.2</version>
</dependency>
JSON Schema Validation 設置項
rest-assured的json-schema-validator
module使用Francis Galiegue的json-schema-validator (fge
) 庫來進行驗證。 如果您想配置使用基礎fge
庫悉尾,你可以像下面例子中:
@Test
public void testsix(){
// Given
JsonSchemaFactory jsonSchemaFactory = JsonSchemaFactory.newBuilder().setValidationConfiguration(ValidationConfiguration.newBuilder().setDefaultVersion(DRAFTV4).freeze()).freeze();
// When
get("http://localhost:8889/lotto").then().assertThat().body(matchesJsonSchemaInClasspath("products-schema.json").using(jsonSchemaFactory));
}
using方法允許您進入jsonSchemaFactory
的實例突那,rest-assured在驗證期間也會進行此操作。這種方式允許我們對驗證進行細粒度的配置构眯。
fge
庫也允許驗證狀態(tài)是 checked
或者unchecked
(譯者注:表示不懂)愕难。默認情況,rest-assured使用checked
驗證惫霸,但是如果你想要改變這種方式猫缭,您可以提供一個matcher的JsonSchemaValidatorSettings實例。舉個例子:
get("/products").then().assertThat().body(matchesJsonSchemaInClasspath("products-schema.json").using(settings().with().checkedValidation(false)));
這些settings
方法靜態(tài)導入自 JsonSchemaValidatorSettings類壹店。
注:想要驗證jsonschema猜丹,用testfive方法中寫的驗證就可以了。
Json Schema Validation的靜態(tài)配置
現(xiàn)在想象下您總是使用unchecked驗證硅卢,并且設置默認的json schema版本為3射窒。與其每次都在代碼里進行設置,不如靜態(tài)地進行定義設置将塑。舉個例子:
@Test
public void testeight(){
JsonSchemaValidator.settings = settings().with().jsonSchemaFactory(
JsonSchemaFactory.newBuilder().setValidationConfiguration(ValidationConfiguration.newBuilder().setDefaultVersion(DRAFTV3).freeze()).freeze()).
and().with().checkedValidation(false);
get("http://localhost:8889/lotto").then().assertThat().body(matchesJsonSchemaInClasspath("products-schema.json"));
}
注:DRAFTV3 會報錯脉顿,DRAFTV4是可以成功的。
現(xiàn)在任意一個由JsonSchemaValidator導入的matcher
都會使用DRAFTV3
作為默認版本并且unchecked validation点寥。
想要重置JsonSchemaValidator
到默認設置僅僅需要調用reset
方法:
JsonSchemaValidator.reset();
不使用rest-assured的Json Schema Validation
您也可以在不依賴rest-assured的情況下使用json-schema-validator module艾疟。如想要把json文本表示為String類型的字符串,可以這樣做:
@Test
public void testnine(){
String json = "{\n" +
" \"lotto\":{\n" +
" \"lottoId\":5,\n" +
" \"winning-numbers\":[2,45,34,23,7,5,3],\n" +
" \"winners\":[{\n" +
" \"winnerId\":23,\n" +
" \"numbers\":[2,45,34,23,3,5]\n" +
" },{\n" +
" \"winnerId\":54,\n" +
" \"numbers\":[52,3,12,11,18,22]\n" +
" }]\n" +
" }\n" +
" }";
assertThat(json,matchesJsonSchemaInClasspath("products-schema.json"));
}
匿名式的JSON根節(jié)點驗證
一個JSON文本并不總是有一個命名好的根屬性开财。這里有個驗證這種JSON的例子:
[1, 2, 3]
注:這種json的返回不太好設計汉柒,moco不出這樣的接口。
一個匿名的JSON根屬性可以通過使用$或者空字符串作為路徑來識別责鳍。舉個例子碾褂,通過訪問http://localhost:8889/json這個地址可以獲得一個JSON文本,我們可以使用rest-assured驗證:
@Test
public void testten(){
given()
.when()
.get("http://localhost:8889/json")
.then()
.body("$",hasItems(1,2,3));
}
例2 - XML
XML可以一種通過簡單的方式解析历葛。假設一個POST請求http://localhost:8080/greetXML返回:
{
"description":"返回內容是xml",
"request":{
"uri":"/getxml",
"method":"post",
"forms":{
"firstName":"John",
"lastName":"Doe"
}
},
"response":{
"text":"<?xml version=\"1.0\" encoding=\"UTF-8\" ?> <greeting>\n<firstName>John</firstName>\n<lastName>Doe</lastName>\n</greeting>",
"headers":{
"Content-Type":"text/xml"
}
}
}
換言之正塌,它在請求中返還了一個基于firstname和lastname請求參數(shù)的greeting節(jié)點。您可以通過rest-assured輕易地展現(xiàn)和解析這個例子
//post入?yún)閒orm 表單參數(shù)恤溶,返回類型為xml
given()
.proxy(8888)//連接代理
.formParam("firstName", "John")
.formParam("lastName","Doe")
.when()
.post("http://localhost:8889/getxml")
.then()
.using()
.defaultParser(Parser.XML)//返回類型是xml格式
.body("greeting.firstName",equalTo("John"));//校驗結果
如果您想同時解析firstname和lastname可以這樣做:
//post入?yún)閒orm 表單參數(shù)乓诽,返回類型為xml
given()
.proxy(8888)//連接代理
.formParam("firstName", "John")
.formParam("lastName","Doe")
.when()
.post("http://localhost:8889/getxml")
.then()
.using()
.defaultParser(Parser.XML)//返回類型是xml格式
.body("greeting.firstName",equalTo("John"))
.body("greeting.lastName",equalTo("Doe"));//校驗結果
或者稍微簡短些:
with().formParams("firstName", "John", "lastName", "Doe").when().post("http://localhost:8889/getxml").then().body("greeting.firstName", equalTo("John"), "greeting.lastName", equalTo("Doe"));
XML 命名空間
考慮到您需要使用io.restassured.config.XmlConfig聲明一個命名空間。舉個例子咒程,有一個位于http://localhost:8080
的資源namespace-example
鸠天,返回如下的XML:
{
"description":"返回內容是xml",
"request":{
"uri":"/getxmlwithnamespace",
"method":"get"
},
"response":{
"text":"<?xml version=\"1.0\" encoding=\"UTF-8\" ?> <foo xmlns:ns=\"http://localhost/\">\n<bar>sudo </bar>\n<ns:bar>make me a sandwich!</ns:bar>\n</foo>",
"headers":{
"Content-Type":"text/xml"
}
}
}
可以然后聲明http://localhost/這個URI并且驗證其響應:
@Test
public void testtwelve(){
given().
config(RestAssured.config().xmlConfig(xmlConfig().declareNamespace("test", "http://localhost/"))).
when().
get("http://localhost:8889/getxmlwithnamespace").
then().
body("foo.bar.text()", equalTo("sudo make me a sandwich!")).
body(":foo.:bar.text()", equalTo("sudo ")).
body("foo.test:bar.text()", equalTo("make me a sandwich!"));
}
這個路徑語法遵循Groovy的XmlSlurper語法。注意直到2.6.0的路徑語法都不遵循Groovy的XmlSlurper語法帐姻。請看release notes可以獲知2.6.0之前的版本語法是怎樣的稠集。
注:參考《XML的命名空間》了解本代碼
XPath:
您也可以使用x-path來解析XML響應奶段。舉個例子:
moco接口:
{
"description":"返回內容是xml,入?yún)㈩愋褪莏son",
"request":{
"uri":"/getxmlwithjson",
"method":"post",
"json":{
"firstName":"John",
"lastName":"Doe"
}
},
"response":{
"text":"<?xml version=\"1.0\" encoding=\"UTF-8\" ?> <greeting>\n<firstName>John</firstName>\n<lastName>Doe</lastName>\n</greeting>",
"headers":{
"Content-Type":"text/xml"
}
}
}
驗證方法:
Map map = new HashMap();
map.put("firstName","John");
map.put("lastName","Doe");
given()
.proxy(8888)
.body(map)
.when()
.post("http://localhost:8889/getxmlwithjson")
.then()
.body(hasXPath("/greeting/firstName[text()='John']"));
或者:
given()
.proxy(8888)
.body(map)
.when()
.post("http://localhost:8889/getxmlwithjson")
.then()
.body(hasXPath("/greeting/firstName",containsString("Jo")));
在XPath表達式中使用命名空間剥纷,你需要在配置中啟用這些選項:
xml:
<h:table xmlns:h="http://www.w3.org/TR/html4/">
<h:tr>
<h:td>Apples</h:td>
<h:td>Bananas</h:td>
</h:tr>
</h:table>
代碼如下:此代碼運行不成功痹籍,namespaceContext的實例寫的不對。
@Test
public void testfourteen(){
NamespaceContext namespaceContext = new NamespaceContext() {
@Override
public String getNamespaceURI(String prefix) {
return null;
}
@Override
public String getPrefix(String namespaceURI) {
return null;
}
@Override
public Iterator getPrefixes(String namespaceURI) {
return null;
}
};
given()
.config(RestAssured.config().xmlConfig(xmlConfig().with().namespaceAware(true)))
.proxy(8888)
.when()
.get("http://localhost:8889/getxmlwithnamespacetwo")
.then()
.body(hasXPath("/h:table",namespaceContext,equalTo("111")));
}
Schema和DTD
XML響應體也可以驗證為一個XML Schema (XSD)或DTD.
校驗XSD是指:接口中返回xml響應體晦鞋,針對xml生成xml schema蹲缠,就是xsd后綴的文件,校驗xsd文件和返回的xml響應體是否一致悠垛。
校驗DTD是指:接口中返回的xml響應體中定義了DTD的文檔規(guī)范线定,DTD文檔是以dtd后綴的文件,校驗dtd文件和返回的xml響應體的文檔規(guī)范是否一致鼎文。
DTD文檔是指在xml頭中定義的DOCTYPE規(guī)范渔肩,比如下面的D:\Springboot\AutoTest\Chapter15\src\main\resources\XSD\mybatis-3-config.dtd路徑下的mybatis-3-config.dtd文件就規(guī)范了該xml的文檔規(guī)范。
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"D:\Springboot\AutoTest\Chapter15\src\main\resources\XSD\mybatis-3-config.dtd">
XSD 例子
get("/carRecords").then().assertThat().body(matchesXsd(xsd));
xsd是xml schema definition拇惋,xml文檔的結構定義周偎。
moco的接口返回xml文檔,對xml文檔生成對應的xsd文檔
{
"description":"模擬返回內容是文件",
"request" :
{
"uri":"/getresponsewithfile",
"method":"get"
},
"response" :
{
"file" : "D:/Springboot/AutoTest/Chapter15/src/main/resources/XSD/assertxml.xml"
}
}
xml文檔內容如下:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<mail mailName="郵件客戶端" mailDescription="打開本地郵件客戶端發(fā)送郵件" >
<mailServer sp="163" >
<pop3 key="ip" value="192.168.1.1" />
<pop3 key="port" value="1234" />
<user key="userName" value="abcduser" />
<user key="password" value="dfdf" />
</mailServer>
</mail>
</root>
生成的xsd文檔內容如下:
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:element name="root">
<xs:complexType>
<xs:sequence>
<xs:element ref="mail"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="mail">
<xs:complexType>
<xs:sequence>
<xs:element ref="mailServer"/>
</xs:sequence>
<xs:attribute name="mailDescription" use="required" type="xs:NCName"/>
<xs:attribute name="mailName" use="required" type="xs:NCName"/>
</xs:complexType>
</xs:element>
<xs:element name="mailServer">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" ref="pop3"/>
<xs:element maxOccurs="unbounded" ref="user"/>
</xs:sequence>
<xs:attribute name="sp" use="required" type="xs:integer"/>
</xs:complexType>
</xs:element>
<xs:element name="pop3">
<xs:complexType>
<xs:attribute name="key" use="required" type="xs:NCName"/>
<xs:attribute name="value" use="required" type="xs:NMTOKEN"/>
</xs:complexType>
</xs:element>
<xs:element name="user">
<xs:complexType>
<xs:attribute name="key" use="required" type="xs:NCName"/>
<xs:attribute name="value" use="required" type="xs:NCName"/>
</xs:complexType>
</xs:element>
</xs:schema>
對應的測試代碼中這些寫:
File file = new File("D:\\Springboot\\AutoTest\\Chapter15\\src\\main\\resources\\XSD\\assertxsd.xsd");
given()
.proxy(8888)
.when()
.get("http://localhost:8889/getresponsewithfile")
.then()
.assertThat()
.body(matchesXsd(file));//接口返回內容是xml撑帖,需要把xml轉換成xml schema蓉坎,然后生成一個文件,把文件傳過來作為參數(shù)
DTD 例子
get("/videos").then().assertThat().body(matchesDtd(dtd));
moco的接口:
{
"description":"模擬返回內容是文件",
"request" :
{
"uri":"/getresponsewithDTDfile",
"method":"get"
},
"response" :
{
"file" : "D:/Springboot/AutoTest/Chapter15/src/main/resources/databaseConfig.xml"
}
}
接口返回的文件頭中內容是:
我們需要把http://mybatis.org/dtd/mybatis-3-config.dtd 文件下到本地,在瀏覽器中輸入該地址就能下載到文件
測試代碼:
File file1 = new File("D:\\Springboot\\AutoTest\\Chapter15\\src\\main\\resources\\XSD\\mybatis-3-config.dtd");
given()
.proxy(8888)
.when()
.get("http://localhost:8889/getresponsewithDTDfile")
.then()
.assertThat()
.body(matchesDtd(file1));//需要傳DTD的文件的地址
matchesXsd
和matchesDtd
方法在Hamcrest matchers里胡嘿,你可以從io.restassured.matcher.RestAssuredMatchers
導入蛉艾。