【Rest Assured】用户手册中文版

原文:https://github.com/rest-assured/rest-assured/wiki/Usage
本文github地址:https://github.com/RookieTester/rest-assured-doc

REST Assured是一个可以简化HTTP Builder顶层 基于REST服务的测试过程的Java DSL(针对某一领域,具有受限表达性的一种计算机程序设计语言)。它支持发起POST,GET,PUT,DELETE,OPTIONS,PATCH和HEAD请求,并且可以用来验证和校对这些请求的响应信息。

文章目录

静态导入方法

推荐大家从以下的类中静态导入方法,以提高使用rest-assured的效率。

io.restassured.RestAssured.*
io.restassured.matcher.RestAssuredMatchers.*
org.hamcrest.Matchers.*

如果您想使用Json Schema validation 还应该静态导入这些方法:

io.restassured.module.jsv.JsonSchemaValidator.*

更多使用方法参阅 Json Schema Validation

如果您正在使用SpringMVC,你可以使用spring-mock-mvc 模型的Rest Assured DSL来对Spring的controller层进行单元测试。为此需要从RestAssuredMockMvc静态导入这些方法,而不是io.restassured.RestAssured:

io.restassured.module.mockmvc.RestAssuredMockMvc.*

示例

例1 - JSON

假设某个get请求 (to http://localhost:8080/lotto) 返回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请求并对响应信息进行处理。举个例子,如果想要判断lottoId的值是否等于5,你可以这样做:

get("/lotto").then().body("lotto.lottoId", equalTo(5));

又或许您想要检查winnerId的取值包括23和54:

get("/lotto").then().body("lotto.winners.winnerId", hasItems(23, 54));

注意: equalTohasItems 是 Hamcrest matchers的方法,所以需要静态导入 org.hamcrest.Matchers

注意这里的"json path"语法使用的是Groovy的GPath标注法,不要和Jayway的JsonPath语法混淆。

以BigDecimal返回float和double类型

(译者注:Java在java.math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运算)

您可以对rest-assured和JsonPath进行配置,使之以BigDecimal返回json里的数值类型数据,而不是float或者double。可以参考下面json文本:

{

    "price":12.12 

}

默认情况下您验证price字段是否等于float类型的12.12像这样:

get("/price").then().body("price", is(12.12f));

但是如果想用rest-assured的JsonConfig来配置返回的所有的json数值都为BigDecimal类型:

given().
        config(RestAssured.config().jsonConfig(jsonConfig().numberReturnType(BIG_DECIMAL))).
when().
        get("/price").
then().
        body("price", is(new BigDecimal(12.12));

JSON Schema validation

自从 2.1.0 版本rest-assured开始支持Json Schema validation. 举个例子,在classpath中放置以下的schema文件(译者注:idea的话可以放在resources目录下),products-schema.json:

{
    "$schema": "http://json-schema.org/draft-04/schema#",
    "title": "Product set",
    "type": "array",
    "items": {
        "title": "Product",
        "type": "object",
        "properties": {
            "id": {
                "description": "The unique identifier for a product",
                "type": "number"
            },
            "name": {
                "type": "string"
            },
            "price": {
                "type": "number",
                "minimum": 0,
                "exclusiveMinimum": true
            },
            "tags": {
                "type": "array",
                "items": {
                    "type": "string"
                },
                "minItems": 1,
                "uniqueItems": true
            },
            "dimensions": {
                "type": "object",
                "properties": {
                    "length": {"type": "number"},
                    "width": {"type": "number"},
                    "height": {"type": "number"}
                },
                "required": ["length", "width", "height"]
            },
            "warehouseLocation": {
                "description": "Coordinates of the warehouse with the product",
                "$ref": "http://json-schema.org/geo"
            }
        },
        "required": ["id", "name", "price"]
    }
}

您可以使用这个schema验证(/products)这个请求是否符合规范:

get("/products").then().assertThat().body(matchesJsonSchemaInClasspath("products-schema.json"));

matchesJsonSchemaInClasspath 静态导入自 io.restassured.module.jsv.JsonSchemaValidator 并且我们推荐从这个类中静态导入所有的方法。然而为了使用它需要依赖于json-schema-validator module 或者从这个网页 下载 它, 又或者通过maven添加下面的依赖:

<dependency>
    <groupId>io.rest-assured</groupId>
    <artifactId>json-schema-validator</artifactId>
    <version>3.0.1</version>
</dependency>

JSON Schema Validation 设置项

rest-assured的json-schema-validator module使用Francis Galiegue的json-schema-validator (fge) 库来进行验证。 如果您想配置使用基础fge库,你可以像下面例子中:

// Given
JsonSchemaFactory jsonSchemaFactory = JsonSchemaFactory.newBuilder().setValidationConfiguration(ValidationConfiguration.newBuilder().setDefaultVersion(DRAFTV4).freeze()).freeze();

// When
get("/products").then().assertThat().body(matchesJsonSchemaInClasspath("products-schema.json").using(jsonSchemaFactory));

using方法允许您进入jsonSchemaFactory的实例,rest-assured在验证期间也会进行此操作。这种方式允许我们对验证进行细粒度的配置。

fge库也允许验证状态是 checked或者unchecked(译者注:表示不懂)。默认情况,rest-assured使用checked验证,但是如果你想要改变这种方式,您可以提供一个matcher的JsonSchemaValidatorSettings实例。举个例子:

get("/products").then().assertThat().body(matchesJsonSchemaInClasspath("products-schema.json").using(settings().with().checkedValidation(false)));

这些settings方法静态导入自 JsonSchemaValidatorSettings类。

Json Schema Validation的静态配置###

现在想象下您总是使用unchecked验证,并且设置默认的json schema版本为3。与其每次都在代码里进行设置,不如静态地进行定义设置。举个例子:

JsonSchemaValidator.settings = settings().with().jsonSchemaFactory(
        JsonSchemaFactory.newBuilder().setValidationConfiguration(ValidationConfiguration.newBuilder().setDefaultVersion(DRAFTV3).freeze()).freeze()).
        and().with().checkedValidation(false);

get("/products").then().assertThat().body(matchesJsonSchemaInClasspath("products-schema.json"));

现在任意一个由JsonSchemaValidator导入的matcher都会使用DRAFTV3作为默认版本并且unchecked validation。

想要重置JsonSchemaValidator到默认设置仅仅需要调用reset方法:

JsonSchemaValidator.reset();

不使用rest-assured的Json Schema Validation

您也可以在不依赖rest-assured的情况下使用json-schema-validator module。如想要把json文本表示为String类型的字符串,可以这样做:

import org.junit.Test;
import static io.restassured.module.jsv.JsonSchemaValidator.matchesJsonSchemaInClasspath;
import static org.hamcrest.MatcherAssert.assertThat;
 
public class JsonSchemaValidatorWithoutRestAssuredTest {
 
 
    @Test 
    public void validates_schema_in_classpath() {
        // Given
        String json = ... // Greeting response
 
        // Then
        assertThat(json, matchesJsonSchemaInClasspath("greeting-schema.json"));
    }
}

更多信息请参阅新手入门

匿名式的JSON根节点验证

一个JSON文本并不总是有一个命名好的根属性。这里有个验证这种JSON的例子:

[1, 2, 3]

一个匿名的JSON根属性可以通过使用$或者空字符串作为路径来识别。举个例子,通过访问http://localhost:8080/json这个地址可以获得一个JSON文本,我们可以使用rest-assured验证:

when().
     get("/json").
then().
     body("$", hasItems(1, 2, 3)); // An empty string "" would work as well

例2 - XML

XML可以一种通过简单的方式解析。假设一个POST请求http://localhost:8080/greetXML返回:

<greeting>
   <firstName>{params("firstName")}</firstName>
   <lastName>{params("lastName")}</lastName>
</greeting>

换言之,它在请求中返还了一个基于firstname和lastname请求参数的greeting节点。您可以通过rest-assured轻易地展现和解析这个例子:

given().
         parameters("firstName", "John", "lastName", "Doe").
when().
         post("/greetXML").
then().
         body("greeting.firstName", equalTo("John")).

如果您想同时解析firstname和lastname可以这样做:

given().
         parameters("firstName", "John", "lastName", "Doe").
when().
         post("/greetXML").
then().
         body("greeting.firstName", equalTo("John")).
         body("greeting.lastName", equalTo("Doe"));

或者稍微简短些:

with().parameters("firstName", "John", "lastName", "Doe").when().post("/greetXML").then().body("greeting.firstName", equalTo("John"), "greeting.lastName", equalTo("Doe"));

这里 的链接获取有关语法的更多信息(它遵循 Groovy的 GPath 语法).

XML 命名空间

考虑到您需要使用io.restassured.config.XmlConfig声明一个命名空间。举个例子,有一个位于http://localhost:8080的资源namespace-example,返回如下的XML:

<foo xmlns:ns="http://localhost/">
  <bar>sudo </bar>
  <ns:bar>make me a sandwich!</ns:bar>
</foo>

可以然后声明http://localhost/这个URI并且验证其响应:

given().
        config(RestAssured.config().xmlConfig(xmlConfig().declareNamespace("test", "http://localhost/"))).
when().
         get("/namespace-example").
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之前的版本语法是怎样的。

XPath

您也可以使用x-path来解析XML响应。举个例子:

given().parameters("firstName", "John", "lastName", "Doe").when().post("/greetXML").then().body(hasXPath("/greeting/firstName", containsString("Jo")));

或者

given().parameters("firstName", "John", "lastName", "Doe").post("/greetXML").then().body(hasXPath("/greeting/firstName[text()='John']"));

在XPath表达式中使用命名空间,你需要在配置中启用这些选项:

given().
        config(RestAssured.config().xmlConfig(xmlConfig().with().namespaceAware(true))).
when().
         get("/package-db-xml").
then().
         body(hasXPath("/db:package-database", namespaceContext));

namespaceContext 是一个javax.xml.namespace.NamespaceContext的实例 。

Schema和DTD

XML响应体也可以验证为一个XML Schema (XSD)或DTD.

XSD 例子
get("/carRecords").then().assertThat().body(matchesXsd(xsd));
DTD 例子
get("/videos").then().assertThat().body(matchesDtd(dtd));
matchesXsdmatchesDtd方法在Hamcrest matchers里,你可以从 io.restassured.matcher.RestAssuredMatchers
导入。

例3 - 复杂的解析和验证

这正是rest-assured闪光点所在!由于rest-assured实现了Groovy,它可以从Groovy集合的API的优点中获益。让我们从下面的Groovy例子中开始探索:

def words = ['ant', 'buffalo', 'cat', 'dinosaur']
def wordsWithSizeGreaterThanFour = words.findAll { it.length() > 4 }

在第一行,我们简单地定义了一个包含一些单词的列表,不过第二行更加有趣。
这里我们检索了列表里的所有长度大于4的单词,通过一个叫做findAll的Groovy闭包。
这个闭包有一个内部变量it,代表着列表中当前的元素。
结果是一个新的列表, wordsWithSizeGreaterThanFour,包含buffalo and dinosaur

这里还有一些其它的有趣的方法,我们也可以使用在Groovy集合中:

  • find – 找到第一个匹配闭包谓词(closure predicate)的元素
  • collect – 收集在集合里的每个元素都调用的闭包返回值(collect the return value of calling a closure on each item in a collection)
  • sum – 对集合里的元素进行求和
  • max/min – 返回集合里的最大值/最小值

所以我们如何在使用rest-assured验证XML和JSON响应时利用这些优点?

XML示例

比方说我们有个资源http://localhost:8080/shopping返回如下的XML:

<shopping>
      <category type="groceries">
        <item>Chocolate</item>
        <item>Coffee</item>
      </category>
      <category type="supplies">
        <item>Paper</item>
        <item quantity="4">Pens</item>
      </category>
      <category type="present">
        <item when="Aug 10">Kathryn's Birthday</item>
      </category>
</shopping>

又比如我们想写一个测试来检验类型为groceries的category节点有Chocolate和Coffee这两个项目。在rest-assured可以这样做:

when().
       get("/shopping").
then().
       body("shopping.category.find { it.@type == 'groceries' }.item", hasItems("Chocolate", "Coffee"));

这里发生了什么事?首先使用XML路径shopping.category获取了所有categoriy的一个列表。在这个列表中我们又调用了一个方法,find,来返回有type这个属性且该属性值为groceries的单个category节点。

在这个category上我们接下来继续收集所有相关联的项目(item)。

由于这里与category相关联的项目不止一个,所以会返回一个列表。接下来我们通过Hamcrest matcher的hasItems方法来解析它。

但是如果我们想取得一些项目(item)但又不想进行断言验证该怎么办?您可以参考XmlPath:

// Get the response body as a String
String response = get("/shopping").asString();
// And get the groceries from the response. "from" is statically imported from the XmlPath class
List<String> groceries = from(response).getList("shopping.category.find { it.@type == 'groceries' }.item");

如果groceries是您对这个响应里唯一的关注点,也可以使用一个捷径:

// Get the response body as a String
List<String> groceries = get("/shopping").path("shopping.category.find { it.@type == 'groceries' }.item");
深度优先搜索

实际上之前的例子我们还可以继续简化:

when().
       get("/shopping").
then().
       body("**.find { it.@type == 'groceries' }", hasItems("Chocolate", "Coffee"));

**是一种在XML文件中做深度优先搜索的捷径。

我们搜索第一个type属性值等于"groceries"的节点。注意我们没有在"item"这个XML路径结束。

原因是在category节点返回一个列表的项目值时,自动调用了toString()这个方法(译者注:这两句有啥因果关系我没搞懂)。

JSON示例

假设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

在本例中我们发起一个请求"/store",并且做了一个断言:搜集满足price字段值小于10的所有book数组里的title字段,得到了"Sayings of the Century"和"Moby Dick"这两个结果:

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

就像上面XML的例子,我们使用闭包获取所有price字段值低于10的book数组,并且返回相应的title字段值集合。

然后使用hasItems这个匹配器来断言得到我们预期的结果。使用JsonPath 我们可以用下面的方法替代:

// 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");
例2

考虑下该如何断言所有author字段值长度总和是否大于50的结果。

这是个挺难以回答的问题,也正展示了闭包和Groovy集合的强大之处。在rest-assured里可以:

when().
       get("/store");
then().
       body("store.book.author.collect { it.length() }.sum()", greaterThan(50));

首先我们通过(store.book.author)得到了所有的author字段值,然后使用闭包里的方法{ it.length() }解析这个集合。

它所做的是对列表里的每一个author字段执行一次length()方法,然后返回一个新的列表。在这个列表中,我们再调用sum()方法来求得字符长度的总和。

最终的结果是53,并且我们使用greaterThan匹配器的断言结果是大于50 。
但是实际上可以继续简化这种写法。可以再次参考"words"这个例子

def words = ['ant', 'buffalo', 'cat', 'dinosaur']

Groovy有一个便利的方法可以遍历列表中的所有元素,使用*来调用。举个例子:

def words = ['ant', 'buffalo', 'cat', 'dinosaur']
assert [3, 6, 3, 8] == words*.length()

Groovy返回了一个新的包含words中每个字段字符长度的列表。我们也可以把rest-assured中的这个语法用在author列表中:

when().
       get("/store");
then().
       body("store.book.author*.length().sum()", greaterThan(50)).

当然我们可以使用JsonPath来获取这个结果:

// 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));

其它例子

Micha Kops曾写过一篇很优秀的博客,里面包含大量示例(包括可检出的代码)。您可以由此进入试读

Bas Dijkstra也开展过不少关于rest-assured的开源研究和资源。你可以由此进入试读,如果您想试用或者作出贡献,他的github仓库里有些可以尝试的练习题。

关于float和double

浮点型数字必须和Java的基本类型"float"区分开。举个例子,如果我们看下面的JSON对象:

{

    "price":12.12 

}

如下的测试将会失败,因为我们在拿一个"double"在比较,而不是"float":

get("/price").then().assertThat().body("price", equalTo(12.12));

想用"float"比较的话写法应该是:

get("/price").then().assertThat().body("price", equalTo(12.12f));

语法关注点

当阅读rest-assured的博客时,你也许会看到许多使用"given / expect / when"语法的例子,举个例子:

given().
        param("x", "y").
expect().
        body("lotto.lottoId", equalTo(5)).
when().
        get("/lotto");

这是一种“遗留语法”,这实际上是rest-assured 1.x.版本用来写测试用例的方式。然而这种运作方式令许多用户迷惑甚至恼怒。这是因为一开始没有把"given / when / then"作为主要的技术来使用。所以rest-assured得2.0版本之前差不多不支持这种类似BDD-like测试的标准用法。"given / expect / when"在2.0仍然可用但是"given / when / then"可读性更强所以在测试用例中更为推荐。然而使用"given / expect / when"还有一个好处,就是所有的期望中的错误可以在同时展示出来,这是新语法做不到的(自从预期结果放在了最后面)。这意味着如果你有多个预期结果想要检验你可以:

given().
        param("x", "y").
expect().
        statusCode(400).
        body("lotto.lottoId", equalTo(6)).
when().
        get("/lotto");

rest-assured将同时报告状态码预期和响应体预期结果都是错的。将这些用新语法重写:

given().
        param("x", "y").
when().
        get("/lotto").
then().
        statusCode(400).
        body("lotto.lottoId", equalTo(6));

将会仅仅报告首个预期/断言失败的内容(比如预期状态码是400实际是200),第二个断言将不执行。您将不得不重新运行这个用例以期获取到第二个断言的结果。

语法糖

rest-assured中另一件值得注意的是,有些语法仅仅存在于语法糖中,举个例子,"and"在一行代码中使用可以增强可读性。

given().param("x", "y").and().header("z", "w").when().get("/something").then().assertThat().statusCode(200).and().body("x.y", equalTo("z"));

这等价于:

given().
        param("x", "y").
        header("z", "w").
when().
        get("/something").
then().
        statusCode(200).
        body("x.y", equalTo("z"));

获得响应体信息

你也可以获得响应的内容。比方说你想通过发起一个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方法仅仅返回response本身的一个实例。如何你想获取响应里的值,并将其作为接下来的请求内容,这会很有用。下面是一个叫做title的资源返回的JSON数据:

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

想验证内容类型是JSON格式且标题是My Title,但是还想要从中提取next的值并用来发起请求,下面是使用方法:

String nextTitleLink =
given().
        param("param_name", "param_value").
when().
        get("/title").
then().
        contentType(JSON).
        body("title", equalTo("My Title")).
extract().
        path("_links.next.href");

get(nextTitleLink). ..

如果您想提取多个值,也可以考虑返回整个响应体:

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

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

JSON (使用 JsonPath)

一旦我们取得了响应体,可以使用JsonPath来提取相应的数据:

int lottoId = from(json).getInt("lotto.lottoId");
List<Integer> winnerIds = from(json).get("lotto.winners.winnerId");

或者更高效一些:

JsonPath jsonPath = new JsonPath(json).setRoot("lotto");
int lottoId = jsonPath.getInt("lottoId");
List<Integer> winnerIds = jsonPath.get("winners.winnderId");

注意这里我们独立地使用了JsonPath,而没有依赖rest-assured本身的功能,看getting started guide 获取更多信息。

JsonPath 配置

您可以为JsonPath配置反序列化对象(object de-serializers),举个例子:

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

也可以静态配置好JsonPath,这样所有的JsonPath实例都会共享这个配置:

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

更多JsonPath的内容参照这篇博客

注意这里的JsonPath基于Groovy的GPath,不要和Jayway的搞混了。

XML (使用XmlPath)

您也可以使用XmlPath相应的功能:

String xml = post("/greetXML?firstName=John&lastName=Doe").andReturn().asString();
// Now use XmlPath to get the first and last name
String firstName = from(xml).get("greeting.firstName");
String lastName = from(xml).get("greeting.firstName");

// or a bit more efficiently:
XmlPath xmlPath = new XmlPath(xml).setRoot("greeting");
String firstName = xmlPath.get("firstName");
String lastName = xmlPath.get("lastName");

注意,您可以独立于rest-assured,单独使用XmlPath的功能,更多信息参见getting started guide

XmlPath配置

你可以配置XmlPath的对象反序列化器和字符编码,举个例子:

XmlPath xmlPath = new XmlPath(SOME_XML).using(new XmlPathConfig("UTF-8"));

也可以静态地配置XmlPath,使得所有的实例都能共享这套配置:

XmlPath.config = new XmlPathConfig("UTF-8");

更多关于XmlPath的信息参阅这篇博客

获取某个路径下的值

如您你只是想发起一个请求并返回一个路径下的值,你可以使用一个捷径:

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, jsonPathhtmlPath都是可选项。

Headers, cookies, status等

您也可以获取 header, cookie, 状态行,状态码:

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 和 cookie

header 和 cookie 可以包含同名的多个值。

多个 header

要获取header的所有值,您需要首先从Response对象中获取Headers 对象。您需要首先从Response对象中获取Headers对象。您可以使用Headers.getValues(
)方法返回一个具有所有header值的List列表。

多个 cookie

要获取cookie的所有值,您需要首先从Response对象中获取Cookie对象。您可以使用Cookie.getValues()方法获取所有值,该方法返回包含所有Cookie值的List列表。

详细的 Cookies 信息

如果您需要获取Cookie的路径或过期日期等详细信息,您需要从REST Assured获取一个detailed cookie。您可以使用Response.getDetailedCookie(java.lang.String) 方法获取单个Cookie,包括与给定名称相关联的所有属性。

您还可以使用Response.getDetailedCookies()方法获取所有详细的响应cookies

指定请求数据

除了指定请求参数,您还可以指定header,Cookie,正文和Content Type。

请求HTTP资源

您通常通过调用request specification中的“HTTP方法”执行请求。例如:

when().get("/x"). ..;

其中get是HTTP请求方法。

从REST Assured 3.0.0开始,您可以通过使用该方法为请求使用任何HTTP动词。

when().
       request("CONNECT", "/somewhere").
then().
       statusCode(200);

这将向服务器发送“连接”请求。

参数化

通常您可以这样指定参数:

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

REST Assured将自动尝试基于HTTP方法确定哪个参数类型(即查询或表单参数)。在GET的情况下,查询参数将被自动使用,在POST的情况下将使用表单参数。在某些情况下,重要的是在PUT或POST中分离表单和查询参数。你可以这样使用:

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

参数也可以url上进行设置:

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

参数如果上传的是文件,字节数组,输入流或文本的可以参照Multi-part类型的表单数据部分

多值参数

多值参数是每个参数名称具有多于一个值的参数(即,每个名称的值的列表)。您可以使用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).
then().
         ..

这里 roomNumber 的值My Hotel将被替换为 23.

注意,指定太少或太多的参数将导致错误消息。对于高级用例,您可以从[过滤器](#过滤器)添加,更改,删除(甚至冗余的路径参数)。

Cookie

通常模式下,您可以通过以下方法指定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](http://static.javadoc.io/io.rest-assured/rest-assured/3.0.1/io/restassured/config/HeaderConfig.html)的基础上进行更改。例如:

given().
        config(RestAssuredConfig.config().headerConfig(headerConfig().overwriteHeadersWithName("x"))).
        header("x", "1").
        header("x", "2").
when().
        get("/something").
...

这意味着只有header “x = 2”被发送到服务器

Content Type

given().contentType(ContentType.TEXT). ..
given().contentType("application/json"). ..

请求正文

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。点击这里了解详情。

验证响应数据

您还可以验证状态码,状态行,Cookie,headers,内容类型和正文。

响应体

请参阅使用示例,例如JSONXML.

您还可以将响应正文映射到Java对象,单击这里 了解详细信息。

Cookie

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")). ..

Header

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). ..

内容全匹配

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静态导入的。

计算响应时间

从 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处理时间等,不能做到十分准确)。

认证

REST assured还支持多种认证方案,例如OAuth,摘要,证书,表单和抢占式基本认证。 您可以为每个请求设置身份验证:

given().auth().basic("username", "password"). ..

也可以为所有请求定义身份验证:

RestAssured.authentication = basic("username", "password");

或者您也可以使用 specification.

基本认证

有两种类型的基本认证,抢占和“受质询的基本认证”。

抢占式

服务器在某些情况下给出未授权响应之前发送基本认证凭证,从而减少进行附加连接的开销。 大多数情况下可以这么使用:

given().auth().preemptive().basic("username", "password").when().get("/secured/hello").then().statusCode(200);

受质询的基本认证

使用“受质询的基本认证”时,REST Assured将不提供凭据,除非服务器已明确要求。 这意味着REST Assured将向服务器发出一个附加请求,以便进行质询,然后再次处理相同的请求,但此时会在header中设置基本凭据。

given().auth().basic("username", "password").when().get("/secured/hello").then().statusCode(200);

摘要认证

目前只支持受质询的摘要认证:

given().auth().digest("username", "password").when().get("/secured"). ..

表单认证

表单认证在互联网上非常流行。 它通常与用户在网页上填写其凭据(用户名和密码),然后在按某种类型的登录按钮时发起请求。 提供表单身份验证基础的一个非常简单的HTML页面可能如下所示

<html>
  <head>
    <title>Login</title>
  </head>

  <body>
    <form action="j_spring_security_check" method="POST">
      <table>
        <tr><td>User:&nbsp;</td><td><input type='text' name='j_username'></td></tr>
        <tr><td>Password:</td><td><input type='password' name='j_password'></td></tr>
          <tr><td colspan='2'><input name="submit" type="submit"/></td></tr>
       </table>
        </form>
      </body>
 </html>

也就是说 服务器期望用户填写“j_username”和“j_password”输入字段,然后按“提交”登录。 使用REST Assured,您可以测试受表单身份验证保护的服务,如下所示:

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

在REST中使用此类表单身份验证时,会导致为检索包含登录详细信息的网页而向服务器发出附加请求。 REST Assured将尝试解析此页面并查找两个输入字段(用户名和密码)以及表单操作的URI。 这可能失败,取决于网页的复杂性。 更好的选择是在设置表单身份验证时提供这些详细信息。 在这种情况下,可以:

given().
        auth().form("John", "Doe", new FormAuthConfig("/j_spring_security_check", "j_username", "j_password")).
when().
        get("/formAuth");
then().
        statusCode(200);

这样REST Assured不需要提出额外的请求并解析网页。 还有一个预定义的FormAuthConfig称为springSecurity,如果你使用默认的Spring Security属性,可以使用它:

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

CSRF

如今,服务器要求请求中提供一个CSRF token是常有的事了,这可以抵御多种类型的攻击。rest-assured支持解析并自动给服务器供应一个CSRF token。为此,rest-assured必须先发起一个追加请求来解析该网站(的部分内容)。

你可以通过下面的代码启用对CSRF的支持:

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

现在rest-assured将会自动尝试侦测这个网站是否包含CSRF token机制。为了使rest-assured的暴力破解更加顺利,可能会提供一个CSRF域的名称(这里我们假设我们正在使用Spring的安全默认值,因此我们可以使用预定义的springSecurity表单认证配置):

given().
        auth().form("John", "Doe", springSecurity().withCsrfFieldName("_csrf")).
when().
        get("/formAuth");
then().
        statusCode(200);

我们至此已经告诉rest-assured去查找名为"_csrf"的CSRF域了(然而这虽然会比自动侦测更快,也更容易出错)。

默认情况下CSRF值将会作为一个请求参数,但是如果必要你也可以配置其放在请求的header中:

given().
        auth().form("John", "Doe", springSecurity().withCsrfFieldName("_csrf").sendCsrfTokenAsHeader()).
when().
        get("/formAuth");
then().
        statusCode(200);

OAuth

为了使用OAuth1和OAuth2(关于查询/请求参数签名方面的机制),您需要添加Scribe到classpath中(如果你正在使用2.1.0或者更早之前版本的rest-assured,请参考旧版指南)。如果是maven请添加以下的依赖:

<dependency>
            <groupId>org.scribe</groupId>
            <artifactId>scribe</artifactId>
            <version>1.3.7</version>
            <scope>test</scope>
</dependency>

如果您没有使用maven,可以下载一个Scribe发行包并把它发在classpath下。

OAuth 1

OAuth1要求Scribe在classpath中。为使用auth1的认证您可以:

given().auth().oauth(..). ..

OAuth 2

自从2.5.0版本您可以依赖于Scribe使用OAuth2的认证:

given().auth().oauth2(accessToken). ..

这将会把OAuth2的accessToken放入header中。想要更加显式的操作可以:

given().auth().preemptive().oauth2(accessToken). ..

这里之所以存在given().auth().oauth2(..)这种语法是为了向后兼容(做的是相同的事情)。如果你需要在请求参数中提供一个OAuth2 token,您需要把Scribe放在classpath下,接下来:

given().auth().oauth2(accessToken, OAuthSignature.QUERY_STRING). ..

自定义身份验证

rest-assured允许您创建一个自定义的身份验证。你可以通过实现io.restassured.spi.AuthFilter接口,并作为一个过滤器。假设您的安全机制,是由两个header值相加然后组成一个新的叫做"AUTH"的header(当然这并不安全)。然后您可以这样做(Java 8的语法):

given().
        filter((requestSpec, responseSpec, ctx) -> {
            String header1 = requestSpec.getHeaders().getValue("header1");
            String header2 = requestSpec.getHeaders().getValue("header2");
            requestSpec.header("AUTH", header1 + header2);
            return ctx.next(requestSpec, responseSpec);
        }).
when().
        get("/customAuth").
then().
  statusCode(200);

使用AuthFilter而不是Filter的原因是,当我们执行given().auth().none(). ..类似这样的操作时AuthFilters会被自动移除。

Multi-part 表单数据

通常我们在向服务器传输大容量的数据时(译者注:比如文件)会使用multipart表单数据技术。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-parts"事务也是可能的:

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");

更多高级使用方法可以使用MultiPartSpecBuilder。举个例子:

Greeting greeting = new Greeting();
greeting.setFirstName("John");
greeting.setLastName("Doe");

given().
        multiPart(new MultiPartSpecBuilder(greeting, ObjectMapperType.JACKSON_2)
                .fileName("greeting.json")
                .controlName("text")
                .mimeType("application/vnd.custom+json").build()).
when().
        post("/multipart/json").
then().
        statusCode(200);

你可以通过使用MultiPartConfig指定默认的control名和文件名。举个例子:

given().config(config().multiPartConfig(multiPartConfig().defaultControlName("something-else"))). ..

这就会默认把control名配置为"something-else"而不是"file"。

其它用法请查阅 这篇博客

对象映射

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并发送到请求中。可以有多种方式:

基于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会按照以下的优先级进行序列化:

  1. 使用Jackson 2将对象序列化为JSON(Faster Jackson (databind))
  2. 使用Jackson将对象序列化为JSON(databind)
  3. 使用Gson将对象序列化为JSON
  4. 使用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)。

由HashMap创建JSON

您也可以提供一个Map,由此rest-assured可以创建一个JSON。

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

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

这将会产生一个JSON数据(JSON payload):

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

使用显式序列化器

如果您的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对象。

基于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);
自定义类型content-type反序列化

如果服务端返回一个自定义的content-type,假设是"application/something",你仍然想使用rest-assured的对象映射的话,这有两种方法。你可以使用显式指定的方法或者为自定义的content-type注册一个解析器:

Message message = expect().parser("application/something", Parser.XML).when().get("/message").as(Message.class);

Message message = expect().defaultParser(Parser.XML).when().get("/message").as(Message.class);

你也可以注册一个默认解析器,或者静态式注册一个自定义的解析器,也可以使用模式(specifications)

使用显式反序列化器

如果您的classpath下同时有多个对象或者不在意响应体的content-type,你可以使用显示的反序列化器。

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

配置

您可以使用ObjectMapperConfig配置预定义的对象映射,并传递给细节配置。举个例子,你可以将GSON的命名策略改为LowerCaseWithUnderscores(译者注:一种策略,将大写字母改为小写字母并添加下划线):

RestAssured.config = RestAssuredConfig.config().objectMapperConfig(objectMapperConfig().gsonObjectMapperFactory(
                new GsonObjectMapperFactory() {
                    public Gson create(Class cls, String charset) {
                        return new GsonBuilder().setFieldNamingPolicy(LOWER_CASE_WITH_UNDERSCORES).create();
                    }
                }
        ));

这里为GSON、JAXB、Jackson和Faster Jackson都预定义了用来映射实例的工厂。

自定义

默认情况下rest-assured将会扫描classpath中各种各样的对象映射。如果您想要集成一种对象映射,默认是不支持的,如果您已经做好了封装,可以实现io.restassured.mapper.ObjectMapper 接口。告诉rest-assured使用您的对象映射或者将其作为body方法里的第二个参数:

given().body(myJavaObject, myObjectMapper).when().post("..")

或者您可以静态式定义:

RestAssured.config = RestAssuredConfig.config().objectMapperConfig(new ObjectMapperConfig(myObjectMapper));

更多例子参阅这里

自定义解析器

rest-assured提供了预定义的解析器,例如HTML、XML和JSON的。但是您可以通过注册预置的解析器来解析现在不支持的内容类型:

RestAssured.registerParser(<content-type>, <parser>);

例如注册一个可以解析’application/vnd.uoml+xml’类型的XML解析器:

RestAssured.registerParser("application/vnd.uoml+xml", Parser.XML);

您也可以注销一个解析器:

RestAssured.unregisterParser("application/vnd.uoml+xml");

解析器可以指定于每个请求中:

get(..).then().using().parser("application/vnd.uoml+xml", Parser.XML). ..;

然后使用模式

默认解析器

有时如果响应中不包含任何content-type,指定一个默认的解析器会很有用。

RestAssured.defaultParser = Parser.JSON;

你也可以为一次请求指定默认的解析器:

get("/x").then().using().defaultParser(Parser.JSON). ..

或者使用响应体模式

默认值

rest-assured发起请求时默认使用localhost的8080端口.如果你需要换个端口:

given().port(80). ..

或者简单些:

..when().get("http://myhost.org:80/doSomething");

您也可以改变默认的基本URI、基本路径、端口和认证scheme:

RestAssured.baseURI = "http://myhost.org";
RestAssured.port = 80;
RestAssured.basePath = "/resource";
RestAssured.authentication = basic("username", "password");
RestAssured.rootPath = "x.y.z";

这意味着类似get("/hello")这样的请求实际上会被解析为http://myhost.org:80/resource/hello,附带身份认证中的用户名和密码属性。关于设置根路径参考这里。其它的默认值也可以被指定:

RestAssured.filters(..); // List of default filters
RestAssured.requestSpecification = .. // Default request specification
RestAssured.responseSpecification = .. // Default response specification
RestAssured.urlEncodingEnabled = .. // Specify if Rest Assured should URL encoding the parameters
RestAssured.defaultParser = .. // Specify a default parser for response bodies if no registered parser can handle data of the response content-type
RestAssured.registerParser(..) // Specify a parser for the given content-type
RestAssured.unregisterParser(..) // Unregister a parser for the given content-type

您可以设置重置为标准的baseURI (localhost),basePath(空),标准端口(8080),标准根路径(“”),默认身份认证scheme(none)和URL编码启用(true):

RestAssured.reset();

模式复用

与其复制一份响应的断言或者请求参数(的代码)到另一个测试用例中,我们可以使用 RequestSpecBuilder或者ResponseSpecBuilder定义一个规范提案。

举个例子,在多个测试用例中,我们都涉及到这样的内容:判断响应码是否为200,JSON数组x.y的长度是否是2,您可以定义一个ResponseSpecBuilder:

ResponseSpecBuilder builder = new ResponseSpecBuilder();
builder.expectStatusCode(200);
builder.expectBody("x.y.size()", is(2));
ResponseSpecification responseSpec = builder.build();

// Now you can re-use the "responseSpec" in many different tests:
when().
       get("/something").
then().
       spec(responseSpec).
       body("x.y.z", equalTo("something"));

在这个例子中需要重用的数据定义并合并在"responseSpec",并且仅当所有预期都通过时用例才能通过。

您也可以将相同的请求参数重用:

RequestSpecBuilder builder = new RequestSpecBuilder();
builder.addParam("parameter1", "parameterValue");
builder.addHeader("header1", "headerValue");
RequestSpecification requestSpec = builder.build();
  
given().
        spec(requestSpec).
        param("parameter2", "paramValue").
when().
        get("/something").
then().
        body("x.y.z", equalTo("something"));        

这里请求数据合并在"requestSpec"中,由此上面例子中实际请求参数包括两个(“parameter1” 和 “parameter2”)和一个header(“header1”)。

过滤器

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

given().filter(new MyFilter()). ..

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

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

Response Builder

如果您想要通过一个过滤器改变Response ,可以使用ResponseBuilder创建一个基于原始response的新实例。比如把原始响应体改为something:

Response newResponse = new ResponseBuilder().clone(originalResponse).setBody("Something").build();

日志

在大量的用例中,打印出响应或者请求的细节将有助于创建正确的预期、发送准确的请求。为此您可以使用rest-assured预定义的过滤器,或者使用其中的快捷方法。

请求日志

自1.5版本起rest-assured支持对特定的请求打日志,之前的做法是使用RequestLoggingFilter(译者注:应该是通过过滤器进行控制)。注意打印日志后HTTP Builder和HTTP Client会添加额外的header内容。这个过滤器可以只记录特定请求的特定细节。换句话说,你可以不关注RequestLoggingFilter记录的有关实际往服务端发送的内容。因为随后的其它过滤器会在日志记录后改变这个请求。如果你想要记录实际发送的内容,参阅HTTP Client logging docs or use an external tool such Wireshark,或者使用第三方工具例如Wireshark。示例如下:

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-assured2.3.1版本起,您可以仅当认证失败时记录请求或者响应的日志。为请求打日志:

given().log().ifValidationFails(). ..

为响应打日志:

.. .then().log().ifValidationFails(). ..

同时启用对请求和响应的认证失败记录,可以使用LogConfig:

given().config(RestAssured.config().logConfig(logConfig().enableLoggingOfRequestAndResponseIfValidationFails(HEADERS))). ..

认证失败,仅记录header。

还有种针对所有请求的简单写法:

RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();

根路径

为避免在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.rootPath = "x.y";

在许多高级用例中,在根路径上附加一些参数也很有用。您可以使用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));

路径参数

在预定义的路径包含变量时,路径参数会很有用。举个例子:

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")).
       ..

路径参数遵循Java的标准格式语法

注意withArgs方法可以从io.restassured.RestAssured类中静态导入。

有时当所有在根路径中指定的参数都已经验证过了,只想要验证一个不含多余参数的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));

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();

SSL

在多数场景下,SSL能顺利运转,这多亏于HTTP Builder和HTTP Client。如果服务端使用了无效的证书,然而有些例子下还是会出错。最简单的方法您可以使用"relaxed HTTPs validation":

given().relaxedHTTPSValidation().when().get("https://some_server.com"). .. 

也可以为所有的请求静态定义这个配置:

RestAssured.useRelaxedHTTPSValidation();

或者放在请求指定中进行复用。

这将会假定我们使用SSL协议。想要改变需要覆盖relaxedHTTPSValidation方法:

given().relaxedHTTPSValidation("TLS").when().get("https://some_server.com"). .. 

您也可以创建一个Java keystore文件并执行一些细粒度的操作。这并不难,首先阅读指南,然后在rest-assured中使用keystore:

given().keystore("/pathToJksInClassPath", <password>). .. 

或者为每一个请求指定:

RestAssured.keystore("/pathToJksInClassPath", <password>);

您也可以定义一个可复用的keystore。

如果您已经使用密码载入了一个keystore,可以将其作为可信的keystore:

RestAssured.trustStore(keystore);

也可以找到一些相关的示例。

有关更多SSL的高级配置参阅SSL Configuration

SSL 无效主机名

如果证书指定了一个无效的主机名,并且您无需创建和导入一个keystore。自2.2.0起您可以:

RestAssured.config = RestAssured.config().sslConfig(sslConfig().allowAllHostnames());

让所有请求支持所有的主机名。

given().config(RestAssured.config().sslConfig(sslConfig().allowAllHostnames()). .. ;

对于单个请求。

注意如果您使用了"relaxed HTTPs validation",那么allowAllHostnames将会默认启用。

URL 编码

由于rest-assured提供了优秀的自动编码,通常您无需考虑URL编码的事情。在有些用例中仍然需要关闭URL编码的功能。其中一个原因是在使用rest-assured之前可能你已经做过一次编码。为防止两次URL编码您需要告诉rest-assured禁用这个功能。

String response = given().urlEncodingEnabled(false).get("https://jira.atlassian.com:443/rest/api/2.0.alpha1/search?jql=project%20=%20BAM%20AND%20issuetype%20=%20Bug").asString();
..

或者

RestAssured.baseURI = "https://jira.atlassian.com";
RestAssured.port = 443;
RestAssured.urlEncodingEnabled = false;
final String query = "project%20=%20BAM%20AND%20issuetype%20=%20Bug";
String response = get("/rest/api/2.0.alpha1/search?jql={q}", query);
..

代理(proxy)配置

从版本2.3.2开始REST Assured可以更好地支持代理。 例如,如果你有一个代理在localhost端口8888你可以做:

given().proxy("localhost", 8888). .. 

如果服务器在本地环境中运行,可以不指定主机名:

given().proxy(8888). .. // Will assume localhost

要使用HTTPS,需要提供第三个参数(scheme)或使用io.restassured.specification.ProxySpecification。 例如:

given().proxy(host("localhost").withScheme("https")). ..

其中host从io.restassured.specification.ProxySpecification静态导入。

从版本2.7.0开始,您还可以为代理指定抢占式基本身份验证。 例如:

given().proxy(auth("username", "password")).when() ..

其中auth是从io.restassured.specification.ProxySpecification静态导入的,也可以将身份验证与不同的host相结合:

given().proxy(host("http://myhost.org").withAuth("username", "password")). ..

静态代理配置

可以为所有请求配置静态代理,例如:

RestAssured.proxy("localhost", 8888);    

或者:

RestAssured.proxy = host("localhost").withPort(8888);

请求规范代理配置

您还可以创建请求规范并在其中指定代理:

RequestSpecification specification = new RequestSpecBuilder().setProxy("localhost").build();
given().spec(specification). ..

详细配置

详细配置由RestAssuredConfig实例提供,您可以使用它配置HTTP Client的参数以及重定向日志编码器解码器SessionObjectMapperConnectionSSLParamConfig 设置。 例子:

特定请求:

given().config(RestAssured.config().redirect(redirectConfig().followRedirects(false))). ..

或使用RequestSpecBuilder:

RequestSpecification spec = new RequestSpecBuilder().setConfig(RestAssured.config().redirect(redirectConfig().followRedirects(false))).build();

或所有请求:

RestAssured.config = config().redirect(redirectConfig().followRedirects(true).and().maxRedirects(0));

config() and newConfig() can be statically imported from io.restassured.config.RestAssuredConfig.

编码配置

使用[EncoderConfig](http://static.javadoc.io/io.rest-assured/rest-assured/3.0.1/io/restassured/config/EncoderConfig.html),可以指定默认的内容encoding charset(如果没有在content-type头中指定)和查询参数charset为所有请求。 如果未指定内容字符集,则使用ISO-8859-1,如果未指定查询参数字符集,则使用UTF-8。 用法示例:

RestAssured.config = RestAssured.config().encoderConfig(encoderConfig().defaultContentCharset("US-ASCII"));

如果没有通过使用EncoderConfig中的defaultCharsetForContentType方法为此内容类型显式定义字符集,那么还可以指定用于特定内容类型的编码器字符集。 例如:

RestAssured.config = RestAssured.config(config().encoderConfig(encoderConfig().defaultCharsetForContentType("UTF-16", "application/xml")));

这将假设明确指定一个字符集的“application / xml”内容类型的UTF-16编码。 默认情况下,“application / json”被指定为使用“UTF-8”作为默认内容类型,这是由[RFC4627](https://www.ietf.org/rfc/rfc4627.txt)指定的。

避免自动将字符集添加到content-type标头

默认情况下,REST Assured会自动添加字符集标头。 要完全禁用这个,你可以这样配置EncoderConfig

RestAssured.config = RestAssured.config(config().encoderConfig(encoderConfig().appendDefaultContentCharsetToContentTypeIfUndefined(false));

解码配置

使用[DecoderConfig](http://static.javadoc.io/io.rest-assured/rest-assured/3.0.1/io/restassured/config/DecoderConfig.html),您可以将默认响应内容解码字符集设置为 所有响应。 如果您期望使用不同于ISO-8859-1的内容字符集(这是默认字符集),并且响应未在内容类型标头中定义字符集,那么这将非常有用。 用法示例:

RestAssured.config = RestAssured.config().decoderConfig(decoderConfig().defaultContentCharset("UTF-8"));

您还可以使用“DecoderConfig”来指定要应用哪些内容解码器。 当你这样做时,“Accept-Encoding”头部将被自动添加到请求中,并且响应主体将被自动解码。 默认情况下,启用GZIP和DEFLATE解码器。 例如要删除GZIP解码但保留DEFLATE解码,您可以执行以下操作:
您还可以使用“DecoderConfig”来指定要应用哪些内容解码器。 当你这样做时,“Accept-Encoding”头部将被自动添加到请求中,并且响应主体将被自动解码。 默认情况下,启用GZIP和DEFLATE解码器。 例如要删除GZIP解码但保留DEFLATE解码,您可以执行以下操作:

given().config(RestAssured.config().decoderConfig(decoderConfig().contentDecoders(DEFLATE))). ..

如果没有通过使用DecoderConfig中的“defaultCharsetForContentType”方法为此内容类型明确定义字符集,则还可以指定要用于特定内容类型的解码器字符集。 例如:

RestAssured.config = config(config().decoderConfig(decoderConfig().defaultCharsetForContentType("UTF-16", "application/xml")));

这将假设明确指定一个字符集的“application / xml”内容类型的UTF-16编码。 默认情况下,“application / json”使用“UTF-8”作为默认字符集,这是由[RFC4627](https://www.ietf.org/rfc/rfc4627.txt)指定的。

Session配置

使用Session配置,您可以配置REST Assured使用的默认session ID名称。 默认session id名称是JSESSIONID,如果应用程序中的名称不同,并且您希望使用REST Assured的[会话支持](#Session_support),您只需更改它。 用法:

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

重定向 DSL

重定向配置可以使用DSL指定。 例如。

given().redirects().max(12).and().redirects().follow(true).when(). .. 

网络连接配置

让您配置REST Assured的连接设置。 例如,如果要在每个响应后强制关闭Apache HTTP Client连接。 如果您在响应中使用少量数据进行大量快速连续请求,则可能需要执行此操作。然而,如果你正在下载(尤其是大量的)分块,你绝不能每个响应后关闭连接的数据。 默认情况下,连接在每个响应后不关闭。

RestAssured.config = RestAssured.config().connectionConfig(connectionConfig().closeIdleConnectionsAfterEachResponse());

Json Config

JsonPathConfig允许您在REST Assured或 JsonPath使用时配置Json设置。 它让你配置如何处理JSON数字。

RestAssured.config = RestAssured.config().jsonConfig(jsonConfig().numberReturnType(NumberReturnType.BIG_DECIMAL))

HTTP Client 配置

为REST Assured将在执行请求时使用的HTTP Client实例配置属性。 默认情况下,REST Assured会为每个“given”语句创建一个新的http Client实例。 要配置重用,请执行以下操作:

RestAssured.config = RestAssured.config().httpClient(httpClientConfig().reuseHttpClientInstance());

您还可以使用httpClientFactory方法提供自定义HTTP Client实例,例如:

RestAssured.config = RestAssured.config().httpClient(httpClientConfig().httpClientFactory(
         new HttpClientConfig.HttpClientFactory() {

            @Override
            public HttpClient createHttpClient() {
                return new SystemDefaultHttpClient();
            }
        }));

注意,目前你需要提供一个AbstractHttpClient的实例.

也可以配置默认参数等。

SSL 配置

SSLConfig 允许您指定更高级的SSL配置,如信任库,密钥库类型和主机名验证器。 例如:

RestAssured.config = RestAssured.config().sslConfig(sslConfig().with().keystoreType(<type>).and().strictHostnames());

参数配置

ParamConfig 允许您配置在“冲突”时,更新不同的参数。 默认情况下,所有参数都将合并,因此如果您执行以下操作:

given().queryParam("param1", "value1").queryParam("param1", "value2").when().get("/x"). ...

REST Assured将发送一个查询字符串param1 = value1&param1 = value2
如果这不是您想要的,你可以配置REST Assured为* replace *值代替:

given().
        config(config().paramConfig(paramConfig().queryParamsUpdateStrategy(REPLACE))).
        queryParam("param1", "value1").
        queryParam("param1", "value2").
when().
        get("/x"). ..

REST Assured现在将替换param1的值为value2(因为它是最后写的),而不是将它们合并在一起。 您也可以为所有参数类型的每种类型配置统一的更新策略

given().config(config().paramConfig(paramConfig().replaceAllParameters())). ..

也支持在[Spring Mock Mvc模块](# Spring Mock Mvc 模块)(配置[MockMvcParamConfig](http://static.javadoc.io/io.restassured/spring-mock -mvc / 3.0.1 / io / restassured / module / mockmvc / config / MockMvcParamConfig.html)。

Spring Mock Mvc 模块

REST Assured 2.2.0引入了对[Spring Mock Mvc](http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/test/web/servlet/MockMvc.html)的支持,使用 spring-mock-mvc模块。 这意味着您可以单元测试Spring Mvc控制器。 例如给出以下Spring控制器:

@Controller
public class GreetingController {

    private static final String template = "Hello, %s!";
    private final AtomicLong counter = new AtomicLong();

    @RequestMapping(value = "/greeting", method = GET)
    public @ResponseBody Greeting greeting(
            @RequestParam(value="name", required=false, defaultValue="World") String name) {
        return new Greeting(counter.incrementAndGet(), String.format(template, name));
    }
}

你可以使用[RestAssuredMockMvc](http://static.javadoc.io/io.restassured/spring-mock-mvc/3.0.1/io/restassured/module/mockmvc/RestAssuredMockMvc.html)来测试它,像这样:

given().
        standaloneSetup(new GreetingController()).
        param("name", "Johan").
when().
        get("/greeting").
then().
        statusCode(200).
        body("id", equalTo(1)).
        body("content", equalTo("Hello, Johan!"));  

即它与标准的REST Assured语法非常相似。 这使得运行测试真的很快,并且比标准的REST Assured更容易引导环境和使用mock。 你常用的标准REST Assured中的大多数东西都可以使用RestAssured Mock Mvc。 例如(某些)配置,静态规范,日志等等。要使用它,你需要依赖于Spring Mock Mvc模块:

<dependency>
      <groupId>io.rest-assured</groupId>
      <artifactId>spring-mock-mvc</artifactId>
      <version>3.0.1</version>
      <scope>test</scope>
</dependency>

或者[下载](http://dl.bintray.com/johanhaleby/generic/spring-mock-mvc-3.0.1-dist.zip)。

Bootstrapping RestAssuredMockMvc

静态导入方法:

io.restassured.module.mockmvc.RestAssuredMockMvc.*
io.restassured.module.mockmvc.matcher.RestAssuredMockMvcMatchers.*

有关其他静态导入,请参阅文档的[静态导入](#static-imports)部分。

为了使用RestAssuredMockMvc启动测试,您需要使用一组控制器,MockMvc实例或Spring的WebApplicationContext来初始化它。您可以对单个请求执行此操作,如上例所示:

given().standaloneSetup(new GreetingController()). ..

也可以使用静态方法:

RestAssuredMockMvc.standaloneSetup(new GreetingController());

如果静态定义,则不必在DSL中指定任何控制器(或MockMvc或WebApplicationContext实例)。这意味着前面的例子可以写成:

given().
        param("name", "Johan").
when().
        get("/greeting").
then().
        statusCode(200).
        body("id", equalTo(1)).
        body("content", equalTo("Hello, Johan!"));  

异步请求

从版本2.5.0 RestAssuredMockMvc支持异步请求。例如,假设有以下控制器

@Controller
public class PostAsyncController {

    @RequestMapping(value = "/stringBody", method = POST)
    public @ResponseBody
    Callable<String> stringBody(final @RequestBody String body) {
        return new Callable<String>() {
            public String call() throws Exception {
                return body;
            }
        };
    }
}

你可以这样测试:

given().
        body("a string").
when().
        async().post("/stringBody").
then().
        body(equalTo("a string"));

默认超时为1秒。也可以使用DSL更改超时:

given().
        body("a string").
when().
        async().with().timeout(20, TimeUnit.SECONDS).post("/stringBody").
then().
        body(equalTo("a string"));    

还可以使用AsyncConfig),例如:

given().
        config(config().asyncConfig(withTimeout(100, TimeUnit.MILLISECONDS))).
        body("a string").
when().
        async().post("/stringBody").
then().
        body(equalTo("a string"));

withTimeout是从io.restassured.module.mockmvc.config.AsyncConfig静态导入的,只是创建一个具有给定超时的AsyncConfig的快捷方式。全局应用配置以应用于所有请求:

RestAssuredMockMvc.config = RestAssuredMockMvc.config().asyncConfig(withTimeout(100, TimeUnit.MILLISECONDS));

// Request 1
given().
        body("a string").
when().
        async().post("/stringBody").
then().
        body(equalTo("a string"));

// Request 2
given().
        body("another string").
when().
        async().post("/stringBody").
then().
        body(equalTo("a string"));

请求1和2现在将使用默认超时100毫秒。

添加请求后处理器

Spring MockMvc已经对请求后处理器做了支持,并且您也可以在RestAssuredMockMvc中使用。举个例子:

given().postProcessors(myPostProcessor1, myPostProcessor2). ..

请注意,推荐从org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors(例如认证相关的 RequestPostProcessors)加入请求后处理器时,直接使用auth方法,这样可读性更强,当然结果是一样的:

given().auth().with(httpBasic("username", "password")). ..

这里的httpBasic静态导入自SecurityMockMvcRequestPostProcessor

添加结果处理器

Spring MockMvc对结果处理器 做了支持,您也可以在RestAssuredMockMvc中使用。比方说您想要使用原生的MockMvc日志功能:

.. .then().apply(print()). .. 

这里的print静态导入自org.springframework.test.web.servlet.result.MockMvcResultHandlers。请注意如果您正在使用2.6.0或更早版本的rest-assured,需要这样使用结果处理器:

given().resultHandlers(print()). .. 

但是rest-assured2.8.0起不推荐使用这种语法。

使用结果匹配器

Spring MockMvc提供了许多结果匹配器,您可以从中获益。RestAssuredMockMvc也对其中必要的功能进行支持。举个例子,基于某种原因您想要使用结果匹配器验证状态码是否等于200:

given().
        param("name", "Johan").
when().
        get("/greeting").
then().
        assertThat(status().isOk()).
        body("id", equalTo(1)).
        body("content", equalTo("Hello, Johan!"));  

这里的status静态导入自org.springframework.test.web.servlet.result.MockMvcResultMatchers。注意,您可以使用expect方法,功能上和assertThat一样,但是更接近原生的MockMvc的语法。

拦截器

您也可以在请求(译者注:这里指mock请求)曝光之前截住并改变MockHttpServletRequestBuilder。您需要先定义一个MockHttpServletRequestBuilderInterceptor,并在RestAssuredMockMvc中使用:

given().interceptor(myInterceptor). ..

Specifications

正如标准的Res​​t Assured,你可以使用specifications,以便更好地重用。请注意,RestAssuredMockMvc的请求规范构建器称为MockMvcRequestSpecBuilder。同样的ResponseSpecBuilder 也可以在RestAssuredMockMvc中使用。规格可以静态定义,就像标准的Res​​t Assured一样。例如:

RestAssuredMockMvc.requestSpecification = new MockMvcRequestSpecBuilder().addQueryParam("name", "Johan").build();
RestAssuredMockMvc.responseSpecification = new ResponseSpecBuilder().expectStatusCode(200).expectBody("content", equalTo("Hello, Johan!")).build();

given().
        standaloneSetup(new GreetingController()).
when().
        get("/greeting").
then().
        body("id", equalTo(1));

重置 RestAssuredMockMvc

如果您使用了任何静态配置,您可以通过调用RestAssuredMockMvc.reset()方法轻松地将RestAssuredMockMvc重置为其默认状态。

Spring MVC认证

spring-mock-mvc的版本`2.3.0’支持身份验证。例如:

given().auth().principal(..). ..

一些认证方法需要Spring安全在类路径(可选)。也可以静态定义认证:

RestAssuredMockMvc.authentication = principal("username", "password");

其中principal方法是从RestAssuredMockMvc静态导入的。还可以在请求构建器中定义认证方案:

MockMvcRequestSpecification spec = new MockMvcRequestSpecBuilder.setAuth(principal("username", "password")).build();

使用 Spring Security 测试

从版本2.5.0也有更好的支持Spring Security。如果你在类路径中有spring-security-test,你可以这样做:

given().auth().with(httpBasic("username", "password")). ..

其中httpBasic是从SecurityMockMvcRequestPostProcessor静态导入的。这将对请求应用基本认证。为了这个工作,你需要应用SecurityMockMvcConfigurer到MockMvc实例。您可以手动执行此操作:

MockMvc mvc = MockMvcBuilders.webAppContextSetup(context).apply(SecurityMockMvcConfigurers.springSecurity()).build();

or RESTAssuredMockMvc will automatically try to apply the springSecurity configurer automatically if you initalize it with an instance of AbstractMockMvcBuilder, for example when configuring a “web app context”:

given().webAppContextSetup(context).auth().with(httpBasic("username", "password")). ..

Here’s a full example:

import io.restassured.module.mockmvc.RestAssuredMockMvc;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.web.context.WebApplicationContext;

import static io.restassured.module.mockmvc.RestAssuredMockMvc.given;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = MyConfiguration.class)
@WebAppConfiguration
public class BasicAuthExample {

    @Autowired
    private WebApplicationContext context;

    @Before public void
    rest_assured_is_initialized_with_the_web_application_context_before_each_test() {
        RestAssuredMockMvc.webAppContextSetup(context);
    }

    @After public void
    rest_assured_is_reset_after_each_test() {
        RestAssuredMockMvc.reset();
    }

    @Test public void
    basic_auth_example() {
        given().
                auth().with(httpBasic("username", "password")).
        when().
                get("/secured/x").
        then().
                statusCode(200).
                expect(authenticated().withUsername("username"));
    }
}

You can also define authentication for all request, for example:

RestAssuredMockMvc.authentication = with(httpBasic("username", "password"));

where with is statically imported from io.restassured.module.mockmvc.RestAssuredMockMvc. It’s also possible to use a request specification.

注入一个用户

也可以使用Spring Security测试注释,例如@WithMockUser@WithUserDetails。例如,假设您想测试此控制器:

@Controller
public class UserAwareController {

    @RequestMapping(value = "/user-aware", method = GET)
    public
    @ResponseBody
    String userAware(@AuthenticationPrincipal User user) {
        if (user == null || !user.getUsername().equals("authorized_user")) {
            throw new IllegalArgumentException("Not authorized");
        }

        return "Success");
    }
}

您可以看到`userAware’方法需要一个 User 作为参数,我们让Spring Security使用@AuthenticationPrincipal 注入它。要生成测试用户,我们可以这样做:

@WithMockUser(username = "authorized_user")
@Test public void
spring_security_mock_annotations_example() {
    given().
            webAppContextSetup(context).
     when().
            get("/user-aware").
     then().
            statusCode(200).
            body(equalTo("Success")).
            expect(authenticated().withUsername("authorized_user"));
}

注意,也可以不使用注释,而是使用RequestPostProcessor ,例如SecurityMockMvcRequestPostProcessors#user(java.lang.String).

参数相关

MockMvc没有区分不同类型的参数,所以paramformParamqueryParam目前只是委托给MockMvc中的param。 formParam自动添加application / x-www-form-urlencoded内容类型的头部,就像标准的Res​​t Assured一样。

Scala支持

REST Assured 2.6.0引入了将“别名”添加到“then”方法的scala-support模块定义在ResponseMockMvcResponse中调用“Then”。这样做的原因是then在将来可能是Scala中的保留关键字,并且当使用具有此名称的方法时,编译器会发出警告。要启用Then,只需从scala-support模块导入io.restassured.module.scala.RestAssuredSupport.AddThenToResponse类。例如:

import io.restassured.RestAssured.when
import io.restassured.module.scala.RestAssuredSupport.AddThenToResponse
import org.hamcrest.Matchers.equalTo
import org.junit.Test

@Test
def `trying out rest assured in scala with implicit conversion`() {
  when().
          get("/greetJSON").
  Then().
          statusCode(200).
          body("key", equalTo("value"))
}

注意:同样支持 Spring Mock Mvc Module.

可以像这样使用它:

SBT:
libraryDependencies += "io.rest-assured" % "scala-support" % "3.0.1"
Maven:
<dependency>
    <groupId>io.rest-assured</groupId>
    <artifactId>scala-support</artifactId>
    <version>3.0.1</version>
    <scope>test</scope>
</dependency>
Gradle:
testCompile 'io.rest-assured:scala-support:3.0.1'

No build manager:

手动下载 distribution file

Kotlin支持

Kotlin是由JetBrains开发的一种语言,它与Java和REST Assured非常好地集成。当使用它与REST Assured有一件事,必须逃避when,因为它是Kotlin中的保留关键字。例如:

Test fun kotlin_rest_assured_example() {
    given().
            param("firstName", "Johan").
            param("lastName", "Haleby").
    `when`().
            get("/greeting").
    then().
            statusCode(200).
            body("greeting.firstName", equalTo("Johan")).
            body("greeting.lastName", equalTo("Haleby"))
}

为了解决这个问题,创建一个extension function,创建一个别名为when时叫做When

fun RequestSpecification.When(): RequestSpecification {
    return this.`when`()
}

代码现在可以像这样写:

Test fun kotlin_rest_assured_example() {
    given().
            param("firstName", "Johan").
            param("lastName", "Haleby").
    When().
            get("/greeting").
    then().
            statusCode(200).
            body("greeting.firstName", equalTo("Johan")).
            body("greeting.lastName", equalTo("Haleby"))
}

注意,我们不需要任何转义。有关更多详细信息,请参阅this博客文章。

更多信息

其他相关信息,请参考 javadoc:

一些代码示例:

如果你需要支持,可以加入 mailing list.

如果需要专业支持,请联系 johanhaleby.

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值