接口测试 rest-assured 使用指南


注意,如果您正在使用 1.9.0 或者更早的版本请参考旧文档

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

目录

  1. 静态导入方法
  2. 示例
    1. JSON 示例
    2. JSON Schema Validation
    3. XML 示例
    4. 高级用法
    5. XML
    6. JSON
    7. 其它示例
  3. 关于 float 和 double
  4. 语法关注点 (语法糖)
  5. 获得响应体信息
    1. 从已验证的响应体中提取值
    2. JSON (使用 JsonPath)
    3. XML (使用 XmlPath)
    4. 获取某个路径下的值
    5. Headers, cookies, status 等
    6. 获取全部 header 值
    7. 获取全部 cookie 值
    8. 获取详细的 cookie 值
  6. 获得响应信息
    1. 在验证响应之后提取特定的值
    2. JSON (使用 JsonPath)
    3. XML (使用 XmlPath)
    4. 单独使用路径
    5. Headers, cookies, status 等
    6. 获取 header
    7. 获取 cookie
    8. 获取详细的 cookie 值
  7. 指定请求数据
    1. 请求 HTTP 资源
    2. 参数化
    3. 多值参数
    4. 参数不赋值
    5. 路径参数
    6. Cookie
    7. Header
    8. Content-Type
    9. 请求正文
  8. 验证响应信息
    1. 响应体
    2. Cookie
    3. 状态码
    4. Header
    5. Content-Type
    6. 内容全匹配
    7. 关联类型验证
    8. 计算响应时间
  9. 认证
    1. 基本认证
    2. 抢占式的基本认证
    3. 受质询的基本认证
    4. 摘要认证
    5. 表单认证
    6. CSRF
    7. OAuth
    8. OAuth1
    9. OAuth2
  10. Multi-part 类型的表单数据
  11. 对象映射
    1. 序列化
    2. 基于 Content-Type 的序列化
    3. 由 HashMap 创建 JSON
    4. 使用显式序列化器
    5. 反序列化
    6. 基于 Content-Type 的反序列化
    7. 自定义 content-type 的反序列化
    8. 使用显式反序列化器
    9. 配置
    10. 自定义
  12. 解析器
    1. 自定义解析器
    2. 默认解析器
  13. 默认值
  14. 模式复用
  15. 过滤器
    1. Response Builder
  16. 日志
    1. 请求日志
    2. 响应日志
    3. 认证失败日志
  17. 根路径
    1. 路径参数
  18. Session 支持
    1. Session 过滤器
  19. SSL
    1. SSL 无效的主机名
  20. URL 编码
  21. 代理(proxy)配置
    1. 静态代理配置
    2. 请求规范代理配置
  22. 详细配置
    1. 编码配置
    2. 解码配置
    3. Session 配置
    4. 重定向(Redirect)DSL
    5. 网络连接配置
    6. JSON 配置
    7. HTTP 客户端配置
    8. SSL 配置
    9. 参数配置
  23. Spring Mock Mvc 模型
    1. Bootstrapping RestAssuredMockMvc
    2. 异步请求
    3. 添加 Request Post Processors
    4. 添加 Result Handlers
    5. 使用 Result 匹配器
    6. 拦截器
    7. Specifications
    8. 重置 RestAssuredMockMvc
    9. Spring MVC 身份认证
      1. 使用 Spring Security 测试
      2. 注入一个用户
    10. 参数相关
  24. Scala 支持
  25. Kotlin 支持
  26. 更多

静态导入方法

推荐大家从以下的类中静态导入方法,以提高使用 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.*

示例

例一 - 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));

注意: equalTo 和 hasItems 是 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");

xmlPathjsonPathhtmlPath都是可选项。

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

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

要获取 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:

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"));
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,内容类型和正文。

响应体

请参阅使用示例,例如JSON 或 XML.

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

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

内容全匹配

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/hellohttp://code.google.com/p/rest-assured/wiki/Usage#Root_path)。其它的默认值也可以被指定:,附带身份认证中的用户名和密码属性。关于设置根路径参考 [这里](

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.htmlencoding),可以指定默认的内容 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.server.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.server.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.

上一篇:SpringCloud使用traceId跟踪日志解决方案


下一篇:接口测试 Rest-Assured 使用指南