HTTP Contracts

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

HTTP Top-Level Elements

HTTP Top-Level Elements

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

  • request: Mandatory

  • response : Mandatory

  • priority: Optional

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

Groovy
link:{verifier_root_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[role=include]
YAML
link:{verifier_root_path}/src/test/resources/yml/contract.yml[role=include]
link:{verifier_root_path}/src/test/resources/yml/contract.yml[role=include]
...
link:{verifier_root_path}/src/test/resources/yml/contract.yml[role=include]
...
Java
link:{verifier_root_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[role=include]
Kotlin
link:{verifier_root_path}/src/test/resources/kotlin/contract_docs_examples.kts[role=include]

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

HTTP Request

HTTP Request

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

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

Groovy
link:{verifier_root_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[role=include]
YAML
link:{verifier_root_path}/src/test/resources/yml/contract.yml[role=include]
Java
link:{verifier_root_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[role=include]
Kotlin
link:{verifier_root_path}/src/test/resources/kotlin/contract_docs_examples.kts[role=include]

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

以下示例使用了 url

Groovy
link:{verifier_root_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[role=include]
YAML
link:{verifier_root_path}/src/test/resources/yml/contract_rest_with_path.yml[role=include]
Java
link:{verifier_root_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[role=include]
Kotlin
link:{verifier_root_path}/src/test/resources/kotlin/contract_docs_examples.kts[role=include]

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

Groovy
link:{verifier_root_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[role=include]
YAML
link:{verifier_root_path}/src/test/resources/yml/contract.yml[role=include]
...
link:{verifier_root_path}/src/test/resources/yml/contract.yml[role=include]
Java
link:{verifier_root_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[role=include]
Kotlin
link:{verifier_root_path}/src/test/resources/kotlin/contract_docs_examples.kts[role=include]

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

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

Groovy
link:{verifier_root_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[role=include]
YAML
link:{verifier_root_path}/src/test/resources/yml/contract.yml[role=include]
...
link:{verifier_root_path}/src/test/resources/yml/contract.yml[role=include]
Java
link:{verifier_root_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[role=include]
Kotlin
link:{verifier_root_path}/src/test/resources/kotlin/contract_docs_examples.kts[role=include]

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

Groovy
link:{verifier_root_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[role=include]
YAML
link:{verifier_root_path}/src/test/resources/yml/contract.yml[role=include]
...
link:{verifier_root_path}/src/test/resources/yml/contract.yml[role=include]
Java
link:{verifier_root_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[role=include]
Kotlin
link:{verifier_root_path}/src/test/resources/kotlin/contract_docs_examples.kts[role=include]

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

Groovy
link:{verifier_root_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[role=include]
YAML
link:{verifier_root_path}/src/test/resources/yml/contract.yml[role=include]
...
link:{verifier_root_path}/src/test/resources/yml/contract.yml[role=include]
Java
link:{verifier_root_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[role=include]
Kotlin
link:{verifier_root_path}/src/test/resources/kotlin/contract_docs_examples.kts[role=include]

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

Groovy
link:{verifier_root_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/SpringTestMethodBodyBuildersSpec.groovy[role=include]
YAML
link:{verifier_root_path}/src/test/resources/yml/contract_multipart.yml[role=include]
Java
link:{verifier_root_path}/src/test/resources/contractsToCompile/contract_multipart.java[role=include]
Kotlin
link:{verifier_root_path}/src/test/resources/kotlin/multipart.kts[role=include]

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

Coded DSL
  • 直接通过使用映射符号,值可以是动态属性(如 formParameter: $(consumer(…​), producer(…​)))。

  • 通过使用 named(…​) 让你能够设置一个命名参数的方法。一个命名参数能够设置 namecontent。你可以通过使用一个像 named("fileName", "fileContent") 这样一个带有两个参数的方法,或者通过使用一个像 named(name: "fileName", content: "fileContent") 这样的映射符号来调用它。

YAML
  • 多部分参数在 multipart.params 部分设置。

  • 命名参数(对于一个给定的参数名称的 fileNamefileContent)能够在 multipart.named 部分设置。该部分包含 paramName(参数名称)、fileName(文件名称)、fileContent(文件内容)字段。

  • 可以在 matchers.multipart 部分设置动态位。

    • 对于参数,使用 params 部分,该部分能够接受 regex 或一个 predefined 正则表达式。

    • 对于命名参数,使用 named 部分,首先在里面使用 paramName 定义参数名称。然后你可以传递 fileNamefileContent 参数化或者 regexpredefined 正则表达式。

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

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

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
link:{verifier_root_path}/src/test/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/WireMockGroovyDslSpec.groovy[role=include]

HTTP Response

HTTP Response

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

Groovy
link:{verifier_root_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[role=include]
YAML
link:{verifier_root_path}/src/test/resources/yml/contract.yml[role=include]
...
link:{verifier_root_path}/src/test/resources/yml/contract.yml[role=include]
Java
link:{verifier_root_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[role=include]
Kotlin
link:{verifier_root_path}/src/test/resources/kotlin/contract_docs_examples.kts[role=include]

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

在 Groovy DSL 中,你可以引用 org.springframework.cloud.contract.spec.internal.HttpStatus 方法为数字状态提供有意义的状态。例如,你可以调用 OK() 方法获取状态 200 ,或调用 BAD_REQUEST() 方法获取 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() 外的所有正文匹配器都受支持。

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

Groovy
link:{verifier_root_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/XmlMethodBodyBuilderSpec.groovy[role=include]
YAML
link:{verifier_root_path}/src/test/resources/yml/contract_rest_xml.yml[role=include]
Java
link:{verifier_root_path}/src/test/resources/contractsToCompile/contract_xml.java[role=include]
Kotlin
link:{verifier_root_path}/src/test/resources/kotlin/contract_xml.kts[role=include]

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

@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 表达式都必须更新。

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

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

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

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

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

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

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

*/[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 语法引用。

General Namespaced Node Expression Syntax

  • Node using qualified namespace:

/<node-name>
  • 使用和定义一个不合格命名空间的节点:

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

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

  • 使用一个不合格命名空间的节点(其一个祖先定义 xmlns 属性):

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

Asynchronous Support

Asynchronous Support

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

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 方法或属性为你的存根添加延迟。以下示例演示如何执行此操作:

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