HTTP Contracts

此页面描述了契约中最重要的 HTTP 相关部分。

This page describes most important HTTP related parts of the contract.

HTTP Top-Level Elements

HTTP Top-Level Elements

可以在契约定义的顶层闭包中调用以下方法:

You can call the following methods in the top-level closure of a contract definition:

  • request: Mandatory

  • response : Mandatory

  • priority: Optional

以下示例展示了如何定义一个 HTTP 请求契约:

The following example shows how to define an HTTP request contract:

Groovy
Unresolved directive in _dsl-http-top-level-elements.adoc - include::{verifier_root_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[]
YAML
Unresolved directive in _dsl-http-top-level-elements.adoc - include::{verifier_root_path}/src/test/resources/yml/contract.yml[]
Unresolved directive in _dsl-http-top-level-elements.adoc - include::{verifier_root_path}/src/test/resources/yml/contract.yml[]
...
Unresolved directive in _dsl-http-top-level-elements.adoc - include::{verifier_root_path}/src/test/resources/yml/contract.yml[]
...
Java
Unresolved directive in _dsl-http-top-level-elements.adoc - include::{verifier_root_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[]
Kotlin
Unresolved directive in _dsl-http-top-level-elements.adoc - include::{verifier_root_path}/src/test/resources/kotlin/contract_docs_examples.kts[]

如果你想让你的合同有更高的优先级,你需要给 priority 标签或方法传递一个较小的数值。例如,值为 5priority 比值为 10priority 具有更高的优先级。

If you want to make your contract have a higher priority, you need to pass a lower number to the priority tag or method. For example, a priority with a value of 5 has higher priority than a priority with a value of 10.

HTTP Request

HTTP Request

HTTP 协议仅要求在请求中指定方法和 URL。相同的的信息在契约的请求定义中是强制性的。

The HTTP protocol requires only the method and the URL to be specified in a request. The same information is mandatory in request definition of the contract.

以下示例展示了一个请求契约:

The following example shows a contract for a request:

Groovy
Unresolved directive in _dsl-request.adoc - include::{verifier_root_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[]
YAML
Unresolved directive in _dsl-request.adoc - include::{verifier_root_path}/src/test/resources/yml/contract.yml[]
Java
Unresolved directive in _dsl-request.adoc - include::{verifier_root_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[]
Kotlin
Unresolved directive in _dsl-request.adoc - include::{verifier_root_path}/src/test/resources/kotlin/contract_docs_examples.kts[]

你可以指定一个绝对而不是相对 url,但是使用 urlPath 是推荐的方式,因为这样做使测试独立于主机。

You can specify an absolute rather than a relative url, but using urlPath is the recommended way, as doing so makes the tests be host-independent.

以下示例使用了 url

The following example uses url:

Groovy
Unresolved directive in _dsl-request.adoc - include::{verifier_root_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[]
YAML
Unresolved directive in _dsl-request.adoc - include::{verifier_root_path}/src/test/resources/yml/contract_rest_with_path.yml[]
Java
Unresolved directive in _dsl-request.adoc - include::{verifier_root_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[]
Kotlin
Unresolved directive in _dsl-request.adoc - include::{verifier_root_path}/src/test/resources/kotlin/contract_docs_examples.kts[]

request 可能包含查询参数,如下例所示(该示例使用了 urlPath):

request may contain query parameters, as the following example (which uses urlPath) shows:

Groovy
Unresolved directive in _dsl-request.adoc - include::{verifier_root_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[]
YAML
Unresolved directive in _dsl-request.adoc - include::{verifier_root_path}/src/test/resources/yml/contract.yml[]
...
Unresolved directive in _dsl-request.adoc - include::{verifier_root_path}/src/test/resources/yml/contract.yml[]
Java
Unresolved directive in _dsl-request.adoc - include::{verifier_root_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[]
Kotlin
Unresolved directive in _dsl-request.adoc - include::{verifier_root_path}/src/test/resources/kotlin/contract_docs_examples.kts[]

如果一个查询参数在合同中丢失,并不意味着我们期望在查询参数丢失的情况下匹配请求。恰恰相反,这意味着查询参数不存在并不要求匹配请求。

If a query parameter is missing in the contract it doesn’t mean that we expect a request to be matched if the query parameter is missing. Quite the contrary, that means that the query parameter is not necessary to be there for the request to be matched.

request 可能包含额外的请求头,如下例所示:

request can contain additional request headers, as the following example shows:

Groovy
Unresolved directive in _dsl-request.adoc - include::{verifier_root_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[]
YAML
Unresolved directive in _dsl-request.adoc - include::{verifier_root_path}/src/test/resources/yml/contract.yml[]
...
Unresolved directive in _dsl-request.adoc - include::{verifier_root_path}/src/test/resources/yml/contract.yml[]
Java
Unresolved directive in _dsl-request.adoc - include::{verifier_root_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[]
Kotlin
Unresolved directive in _dsl-request.adoc - include::{verifier_root_path}/src/test/resources/kotlin/contract_docs_examples.kts[]

request 可能包含额外的请求 cookie,如下例所示:

request may contain additional request cookies, as the following example shows:

Groovy
Unresolved directive in _dsl-request.adoc - include::{verifier_root_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[]
YAML
Unresolved directive in _dsl-request.adoc - include::{verifier_root_path}/src/test/resources/yml/contract.yml[]
...
Unresolved directive in _dsl-request.adoc - include::{verifier_root_path}/src/test/resources/yml/contract.yml[]
Java
Unresolved directive in _dsl-request.adoc - include::{verifier_root_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[]
Kotlin
Unresolved directive in _dsl-request.adoc - include::{verifier_root_path}/src/test/resources/kotlin/contract_docs_examples.kts[]

request 可能包含一个请求正文,如下例所示:

request may contain a request body, as the following example shows:

Groovy
Unresolved directive in _dsl-request.adoc - include::{verifier_root_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[]
YAML
Unresolved directive in _dsl-request.adoc - include::{verifier_root_path}/src/test/resources/yml/contract.yml[]
...
Unresolved directive in _dsl-request.adoc - include::{verifier_root_path}/src/test/resources/yml/contract.yml[]
Java
Unresolved directive in _dsl-request.adoc - include::{verifier_root_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[]
Kotlin
Unresolved directive in _dsl-request.adoc - include::{verifier_root_path}/src/test/resources/kotlin/contract_docs_examples.kts[]

request 可能包含多部分元素。要包含多部分元素,请使用 multipart 方法/章节,如下例所示:

request can contain multipart elements. To include multipart elements, use the multipart method/section, as the following examples show:

Groovy
Unresolved directive in _dsl-request.adoc - include::{verifier_root_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/SpringTestMethodBodyBuildersSpec.groovy[]
YAML
Unresolved directive in _dsl-request.adoc - include::{verifier_root_path}/src/test/resources/yml/contract_multipart.yml[]
Java
Unresolved directive in _dsl-request.adoc - include::{verifier_root_path}/src/test/resources/contractsToCompile/contract_multipart.java[]
Kotlin
Unresolved directive in _dsl-request.adoc - include::{verifier_root_path}/src/test/resources/kotlin/multipart.kts[]

在前面的示例中,我们通过以下两种方式定义了参数:

In the preceding example, we defined parameters in either of two ways:

Coded DSL
  • Directly, by using the map notation, where the value can be a dynamic property (such as formParameter: $(consumer(…​), producer(…​))).

  • By using the named(…​) method that lets you set a named parameter. A named parameter can set a name and content. You can call it either by using a method with two arguments, such as named("fileName", "fileContent"), or by using a map notation, such as named(name: "fileName", content: "fileContent").

YAML
  • The multipart parameters are set in the multipart.params section.

  • The named parameters (the fileName and fileContent for a given parameter name) can be set in the multipart.named section. That section contains the paramName (the name of the parameter), fileName (the name of the file), fileContent (the content of the file) fields.

  • The dynamic bits can be set in the matchers.multipart section.

    • For parameters, use the params section, which can accept regex or a predefined regular expression.

    • For named parameters, use the named section where you first define the parameter name with paramName. Then you can pass the parametrization of either fileName or fileContent in a regex or in a predefined regular expression.

对于 named(…​) 部分,您始终需要添加一对 value(producer(…​), consumer(…​)) 调用。只设置 DSL 属性(如只设置 value(producer(…​)) 或只设置 file(…​))不起作用。有关更多信息,请查看此 issue

For the named(…​) section you always have to add a pair of value(producer(…​), consumer(…​)) calls. Just setting DSL properties such as just value(producer(…​)) or just file(…​) will not work. Check this issue for more information.

从上例中的契约来看,生成的测试和存根如下所示:

From the contract in the preceding example, the generated test and stub look as follows:

Test
// given:
  MockMvcRequestSpecification request = given()
    .header("Content-Type", "multipart/form-data;boundary=AaB03x")
    .param("formParameter", "\"formParameterValue\"")
    .param("someBooleanParameter", "true")
    .multiPart("file", "filename.csv", "file content".getBytes());

 // when:
  ResponseOptions response = given().spec(request)
    .put("/multipart");

 // then:
  assertThat(response.statusCode()).isEqualTo(200);
Stub
Unresolved directive in _dsl-request.adoc - include::{verifier_root_path}/src/test/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockGroovyDslSpec.groovy[]

HTTP Response

HTTP Response

响应必须包含一个 HTTP 状态代码,并且可以包含其他信息。以下代码显示一个示例:

The response must contain an HTTP status code and may contain other information. The following code shows an example:

Groovy
Unresolved directive in _dsl-response.adoc - include::{verifier_root_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[]
YAML
Unresolved directive in _dsl-response.adoc - include::{verifier_root_path}/src/test/resources/yml/contract.yml[]
...
Unresolved directive in _dsl-response.adoc - include::{verifier_root_path}/src/test/resources/yml/contract.yml[]
Java
Unresolved directive in _dsl-response.adoc - include::{verifier_root_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[]
Kotlin
Unresolved directive in _dsl-response.adoc - include::{verifier_root_path}/src/test/resources/kotlin/contract_docs_examples.kts[]

除了状态之外,响应可能包含标头、Cookie 和正文,以与请求中指定的方式相同(请参阅 HTTP Request)。

Besides status, the response may contain headers, cookies, and a body, which are specified the same way as in the request (see HTTP Request).

在 Groovy DSL 中,你可以引用 org.springframework.cloud.contract.spec.internal.HttpStatus 方法为数字状态提供有意义的状态。例如,你可以调用 OK() 方法获取状态 200 ,或调用 BAD_REQUEST() 方法获取 400

In the Groovy DSL, you can reference the org.springframework.cloud.contract.spec.internal.HttpStatus methods to provide a meaningful status instead of a digit. For example, you can call OK() for a status 200 or BAD_REQUEST() for 400.

XML Support for HTTP

XML Support for HTTP

对于 HTTP 合同,我们还支持在请求和响应正文中使用 XML。XML 正文必须在 body 元素中作为 StringGString 传递。此外,可以为请求和响应提供正文匹配器。在 jsonPath(…​) 方法处,应使用 org.springframework.cloud.contract.spec.internal.BodyMatchers.xPath 方法,以第一个参数的形式提供所需 xPath,并以第二个参数的形式提供适当的 MatchingType。除 byType() 外的所有正文匹配器都受支持。

For HTTP contracts, we also support using XML in the request and response body. The XML body has to be passed within the body element as a String or GString. Also, body matchers can be provided for both the request and the response. In place of the jsonPath(…​) method, the org.springframework.cloud.contract.spec.internal.BodyMatchers.xPath method should be used, with the desired xPath provided as the first argument and the appropriate MatchingType as the second argument. All the body matchers apart from byType() are supported.

以下示例显示了一个在响应正文中包含 XML 的 Groovy DSL 合同:

The following example shows a Groovy DSL contract with XML in the response body:

Groovy
Unresolved directive in _dsl-xml.adoc - include::{verifier_root_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/XmlMethodBodyBuilderSpec.groovy[]
YAML
Unresolved directive in _dsl-xml.adoc - include::{verifier_root_path}/src/test/resources/yml/contract_rest_xml.yml[]
Java
Unresolved directive in _dsl-xml.adoc - include::{verifier_root_path}/src/test/resources/contractsToCompile/contract_xml.java[]
Kotlin
Unresolved directive in _dsl-xml.adoc - include::{verifier_root_path}/src/test/resources/kotlin/contract_xml.kts[]

以下示例显示了一个为响应正文中的 XML 自动生成的测试:

The following example shows an automatically generated test for XML in the response body:

@Test
public void validate_xmlMatches() throws Exception {
	// given:
	MockMvcRequestSpecification request = given()
				.header("Content-Type", "application/xml");

	// when:
	ResponseOptions response = given().spec(request).get("/get");

	// then:
	assertThat(response.statusCode()).isEqualTo(200);
	// and:
	DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance()
					.newDocumentBuilder();
	Document parsedXml = documentBuilder.parse(new InputSource(
				new StringReader(response.getBody().asString())));
	// and:
	assertThat(valueFromXPath(parsedXml, "/test/list/elem/text()")).isEqualTo("abc");
	assertThat(valueFromXPath(parsedXml,"/test/list/elem[2]/text()")).isEqualTo("def");
	assertThat(valueFromXPath(parsedXml, "/test/duck/text()")).matches("[0-9]\{3}");
	assertThat(nodeFromXPath(parsedXml, "/test/duck/xxx")).isNull();
	assertThat(valueFromXPath(parsedXml, "/test/alpha/text()")).matches("[\\p\{L}]*");
	assertThat(valueFromXPath(parsedXml, "/test/*/complex/text()")).isEqualTo("foo");
	assertThat(valueFromXPath(parsedXml, "/test/duck/@type")).isEqualTo("xtype");
	}

XML Support for Namespaces

支持命名空间 XML。但是,用于选择命名空间内容的任何 XPath 表达式都必须更新。

Namespaced XML is supported. However, any XPath expresssions used to select namespaced content must be updated.

考虑以下显式命名空间 XML 文档:

Consider the following explicitly namespaced XML document:

<ns1:customer xmlns:ns1="http://demo.com/customer">
    <email>customer@test.com</email>
</ns1:customer>

用于选择电子邮件地址的 XPath 表达式为:/ns1:customer/email/text()

The XPath expression to select the email address is: /ns1:customer/email/text().

请当心,因为不合格的表达式 (/customer/email/text()) 导致 ""

Beware as the unqualified expression (/customer/email/text()) results in "".

对于使用不合格命名空间的内容,该表达式更加详细。考虑以下使用不合格命名空间的 XML 文档:

For content that uses an unqualified namespace, the expression is more verbose. Consider the following XML document that uses an unqualified namespace:

<customer xmlns="http://demo.com/customer">
    <email>customer@test.com</email>
</customer>

用于选择电子邮件地址的 XPath 表达式为

The XPath expression to select the email address is

*/[local-name()='customer' and namespace-uri()='http://demo.com/customer']/*[local-name()='email']/text()

请注意,因为不合格的表达式 (/customer/email/text()*/[local-name()='customer' and namespace-uri()='http://demo.com/customer']/email/text()) 会导致 ""。即使是子元素也必须使用 local-name 语法引用。

Beware, as the unqualified expressions (/customer/email/text() or */[local-name()='customer' and namespace-uri()='http://demo.com/customer']/email/text()) result in "". Even the child elements have to be referenced with the local-name syntax.

General Namespaced Node Expression Syntax

  • Node using qualified namespace:

/<node-name>
  • Node using and defining an unqualified namespace:

/*[local-name=()='<node-name>' and namespace-uri=()='<namespace-uri>']

在某些情况下,您可以忽略 namespace_uri 部分,但这样做可能会导致歧义。

In some cases, you can omit the namespace_uri portion, but doing so may lead to ambiguity.

  • Node using an unqualified namespace (one of its ancestor’s defines the xmlns attribute):

/*[local-name=()='<node-name>']

Asynchronous Support

Asynchronous Support

如果你在服务器端使用异步通信(你的控制器返回 CallableDeferredResult 等),则必须在合同中在 response 部分提供一个 async() 方法。以下代码显示了一个示例:

If you use asynchronous communication on the server side (your controllers are returning Callable, DeferredResult, and so on), then, inside your contract, you must provide an async() method in the response section. The following code shows an example:

Groovy
org.springframework.cloud.contract.spec.Contract.make {
    request {
        method GET()
        url '/get'
    }
    response {
        status OK()
        body 'Passed'
        async()
    }
}
YAML
response:
    async: true
Java
class contract implements Supplier<Collection<Contract>> {

	@Override
	public Collection<Contract> get() {
		return Collections.singletonList(Contract.make(c -> {
			c.request(r -> {
				// ...
			});
			c.response(r -> {
				r.async();
				// ...
			});
		}));
	}

}
Kotlin
import org.springframework.cloud.contract.spec.ContractDsl.Companion.contract

contract {
    request {
        // ...
    }
    response {
        async = true
        // ...
    }
}

你还可以使用 fixedDelayMilliseconds 方法或属性为你的存根添加延迟。以下示例演示如何执行此操作:

You can also use the fixedDelayMilliseconds method or property to add delay to your stubs. The following example shows how to do so:

Groovy
org.springframework.cloud.contract.spec.Contract.make {
    request {
        method GET()
        url '/get'
    }
    response {
        status 200
        body 'Passed'
        fixedDelayMilliseconds 1000
    }
}
YAML
response:
    fixedDelayMilliseconds: 1000
Java
class contract implements Supplier<Collection<Contract>> {

	@Override
	public Collection<Contract> get() {
		return Collections.singletonList(Contract.make(c -> {
			c.request(r -> {
				// ...
			});
			c.response(r -> {
				r.fixedDelayMilliseconds(1000);
				// ...
			});
		}));
	}

}
Kotlin
import org.springframework.cloud.contract.spec.ContractDsl.Companion.contract

contract {
    request {
        // ...
    }
    response {
        delay = fixedMilliseconds(1000)
        // ...
    }
}