Dynamic properties
契约可以包含一些动态属性:时间戳、ID等。您不想强制消费者存根其时钟来始终返回相同的时间值,以便存根与之匹配。 对于Groovy DSL,您可以在契约中通过两种方式提供动态部分:直接将它们传递到正文中或将它们设置在一个称为`bodyMatchers`的单独部分中。
2.0.0 之前,这些是使用 |
对于YAML,您只能使用`matchers`部分。
matchers
中的条目必须引用有效载荷中的现有元素。有关更多信息,请参阅 this issue。
Dynamic Properties inside the Body
本节只对编码的 DSL(Groovy、Java 等)有效。请参阅Dynamic Properties in the Matchers Sections 部分,了解类似特性的 YAML 示例。
您可以使用`value`方法在正文内设置属性,或者,如果您使用Groovy映射表示法,则可以使用`$()`设置属性。以下示例显示了如何使用`value`方法设置动态属性:
value(consumer(...), producer(...))
value(c(...), p(...))
value(stub(...), test(...))
value(client(...), server(...))
$(consumer(...), producer(...))
$(c(...), p(...))
$(stub(...), test(...))
$(client(...), server(...))
两种方法同样适用。`stub`和`client`方法是`consumer`方法的别名。后面的部分将仔细介绍您可以对这些值执行哪些操作。
Regular Expressions
本节只对 Groovy DSL 有效。请参阅Dynamic Properties in the Matchers Sections 部分,了解类似特性的 YAML 示例。
您可以在契约DSL中使用正则表达式来编写请求。当您想要指定应该为遵循给定模式的请求提供给定的响应时,这样做尤其有用。此外,当您需要对测试和服务器端测试同时使用模式而不用具体值时,可以使用正则表达式。
确保正则表达式符合一个序列的整个区域,因为在内部会调用 Pattern.matches()
。例如,abc`不符合`aabc
,但`.abc`符合。还有一些其他known limitations。
以下示例显示了如何使用正则表达式来编写请求:
Unresolved directive in dsl-dynamic-properties.adoc - include::{verifier_root_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[]
Unresolved directive in dsl-dynamic-properties.adoc - include::{verifier_root_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[]
Unresolved directive in dsl-dynamic-properties.adoc - include::{verifier_root_path}/src/test/resources/kotlin/contract_docs_examples.kts[]
您还可以仅使用正则表达式提供通信的一方。如果您这样做,则契约引擎将自动提供与所提供的正则表达式匹配的生成字符串。以下代码显示了Groovy的示例:
Unresolved directive in dsl-dynamic-properties.adoc - include::{verifier_root_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/SpringTestMethodBodyBuildersSpec.groovy[]
在上一个示例中,通信的另一方为请求和响应生成了各自的数据。
Spring Cloud Contract带有您可以在契约中使用的一系列预定义正则表达式,如下面的示例所示:
Unresolved directive in dsl-dynamic-properties.adoc - include::{contract_spec_path}/src/main/java/org/springframework/cloud/contract/spec/internal/RegexPatterns.java[]
在您的契约中,您可以按如下方式使用它(Groovy DSL的示例):
Unresolved directive in dsl-dynamic-properties.adoc - include::{verifier_root_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/SpringTestMethodBodyBuildersSpec.groovy[]
为了更简单,您可以使用一组预定义的对象,它们会自动解析希望传递正则表达式。所有这些方法都使用`any`前缀开头,如下所示:
Unresolved directive in dsl-dynamic-properties.adoc - include::{contract_spec_path}/src/main/java/org/springframework/cloud/contract/spec/internal/RegexCreatingProperty.java[]
以下示例显示了如何引用这些方法:
Unresolved directive in dsl-dynamic-properties.adoc - include::{verifier_root_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[]
Unresolved directive in dsl-dynamic-properties.adoc - include::{verifier_root_path}/src/test/resources/kotlin/contract_docs_examples.kts[]
Passing Optional Parameters
此部分仅对 Groovy DSL 有效。有关类似功能的 YAML 示例,请参见 Dynamic Properties in the Matchers Sections 部分。
可以在契约中提供可选参数。但是,你只能为以下内容提供可选参数:
-
请求的 STUB 端
-
响应的 TEST 端
以下示例演示如何提供可选参数:
Unresolved directive in dsl-dynamic-properties.adoc - include::{verifier_root_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[]
Unresolved directive in dsl-dynamic-properties.adoc - include::{verifier_root_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[]
Unresolved directive in dsl-dynamic-properties.adoc - include::{verifier_root_path}/src/test/resources/kotlin/contract_docs_examples.kts[]
通过用 optional()
方法包装一部分内容,你创建了一个必须出现 0 次或更多次的正则表达式。
如果你使用 Spock,则会从上一个示例生成以下测试:
Unresolved directive in dsl-dynamic-properties.adoc - include::{verifier_root_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[]
还将生成以下存根:
Unresolved directive in dsl-dynamic-properties.adoc - include::{plugins_path}/spring-cloud-contract-converters/src/test/groovy/org/springframework/cloud/contract/verifier/wiremock/DslToWireMockClientConverterSpec.groovy[]
Calling Custom Methods on the Server Side
本节只对 Groovy DSL 有效。请参阅Dynamic Properties in the Matchers Sections 部分,了解类似特性的 YAML 示例。
可以在测试期间在服务器端运行定义一个方法调用。可以在配置中将此方法添加到定义为 baseClassForTests
的类中。以下代码显示了测试用例的契约部分示例:
Unresolved directive in dsl-dynamic-properties.adoc - include::{verifier_root_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[]
Unresolved directive in dsl-dynamic-properties.adoc - include::{verifier_root_path}/src/test/resources/contractsToCompile/contract_docs_examples.java[]
Unresolved directive in dsl-dynamic-properties.adoc - include::{verifier_root_path}/src/test/resources/kotlin/contract_docs_examples.kts[]
以下代码显示了测试用例的基本类部分:
Unresolved directive in dsl-dynamic-properties.adoc - include::{plugins_path}/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/bootSimple/src/test/groovy/org/springframework/cloud/contract/verifier/twitter/places/BaseMockMvcSpec.groovy[]
您无法同时使用 String
和 execute
来执行连接。例如,调用 header('Authorization', 'Bearer ' + execute('authToken()'))
将导致结果不正确。而是调用 header('Authorization', execute('authToken()'))
并确保 authToken()
方法返回您需要的一切。
对象从 JSON 中读取的类型可以是以下之一,这取决于 JSON 路径:
-
String
:如果你在 JSON 中指向String
值。 -
JSONArray
:如果你在 JSON 中指向List
。 -
Map
:如果你在 JSON 中指向Map
。 -
Number
:如果你在 JSON 中指向Integer
、Double
和其他数字类型。 -
Boolean
:如果你在 JSON 中指向Boolean
。
在契约的请求部分中,可以指定 body
应该从一个方法中获取。
您必须提供消费者端和生产者端。execute
部分应用于整个主体,不应用于部分。
以下示例演示如何从 JSON 中读取一个对象:
Unresolved directive in dsl-dynamic-properties.adoc - include::{verifier_root_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MethodBodyBuilderSpec.groovy[]
前面的示例会导致在请求正文中调用 hashCode()
方法。它应该类似于以下代码:
// given:
MockMvcRequestSpecification request = given()
.body(hashCode());
// when:
ResponseOptions response = given().spec(request)
.get("/something");
// then:
assertThat(response.statusCode()).isEqualTo(200);
Referencing the Request from the Response
最好的情况是提供固定值,但有时候你需要在响应中引用一个请求。
如果你在 Groovy DSL 中编写契约,则可以使用 fromRequest()
方法,该方法允许你从 HTTP 请求中引用一堆元素。可以使用以下选项:
-
fromRequest().url()
:返回请求 URL 和查询参数。 -
fromRequest().query(String key)
:返回具有给定名称的第一个查询参数。 -
fromRequest().query(String key, int index)
:返回具有给定名称的第 n 个查询参数。 -
fromRequest().path()
:返回完整路径。 -
fromRequest().path(int index)
:返回第 n 个路径元素。 -
fromRequest().header(String key)
:返回具有给定名称的第一个标头。 -
fromRequest().header(String key, int index)
:返回具有给定名称的第 n 个标头。 -
fromRequest().body()
:返回完整的请求正文。 -
fromRequest().body(String jsonPath)
:返回与 JSON 路径匹配的请求中的元素。
如果您使用 YAML 合同定义或 Java 定义,则必须使用 {{{ }}}
Handlebars 表示法和自定义 Spring Cloud Contract 函数来实现此目的。在这种情况下,您可以使用以下选项:
-
{{{ request.url }}}
:返回请求 URL 和查询参数。 -
{{{ request.query.key.[index] }}}
: 返回给定名称的第 n 个查询参数。例如,对于键thing
,第一个条目为{{{ request.query.thing.[0] }}}
-
{{{ request.path }}}
:返回完整路径。 -
{{{ request.path.[index] }}}
:返回第 n 个路径元素。例如,第一个条目是`
{{{ request.path.[0] }}} -
{{{ request.headers.key }}}
:返回具有给定名称的第一个标头。 -
{{{ request.headers.key.[index] }}}
:返回第 n 个具有给定名称的标头。 -
{{{ request.body }}}
:返回完整的请求正文。 -
{{{ jsonpath this 'your.json.path' }}}
:返回与 JSON 路径相匹配的请求中的元素。例如,对于 JSON 路径$.here
,请使用{{{ jsonpath this '$.here' }}}
考虑以下契约:
Unresolved directive in dsl-dynamic-properties.adoc - include::{verifier_root_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/SpringTestMethodBodyBuildersSpec.groovy[]
Unresolved directive in dsl-dynamic-properties.adoc - include::{verifier_root_path}/src/test/resources/yml/contract_reference_request.yml[]
package contracts.beer.rest;
import java.util.function.Supplier;
import org.springframework.cloud.contract.spec.Contract;
import static org.springframework.cloud.contract.verifier.util.ContractVerifierUtil.map;
class shouldReturnStatsForAUser implements Supplier<Contract> {
@Override
public Contract get() {
return Contract.make(c -> {
c.request(r -> {
r.method("POST");
r.url("/stats");
r.body(map().entry("name", r.anyAlphaUnicode()));
r.headers(h -> {
h.contentType(h.applicationJson());
});
});
c.response(r -> {
r.status(r.OK());
r.body(map()
.entry("text",
"Dear {{{jsonPath request.body '$.name'}}} thanks for your interested in drinking beer")
.entry("quantity", r.$(r.c(5), r.p(r.anyNumber()))));
r.headers(h -> {
h.contentType(h.applicationJson());
});
});
});
}
}
package contracts.beer.rest
import org.springframework.cloud.contract.spec.ContractDsl.Companion.contract
contract {
request {
method = method("POST")
url = url("/stats")
body(mapOf(
"name" to anyAlphaUnicode
))
headers {
contentType = APPLICATION_JSON
}
}
response {
status = OK
body(mapOf(
"text" to "Don't worry $\{fromRequest().body("$.name")} thanks for your interested in drinking beer",
"quantity" to v(c(5), p(anyNumber))
))
headers {
contentType = fromRequest().header(CONTENT_TYPE)
}
}
}
运行 JUnit 测试生成会生成类似于以下示例的测试:
// given:
MockMvcRequestSpecification request = given()
.header("Authorization", "secret")
.header("Authorization", "secret2")
.body("{\"foo\":\"bar\",\"baz\":5}");
// when:
ResponseOptions response = given().spec(request)
.queryParam("foo","bar")
.queryParam("foo","bar2")
.get("/api/v1/xxxx");
// then:
assertThat(response.statusCode()).isEqualTo(200);
assertThat(response.header("Authorization")).isEqualTo("foo secret bar");
// and:
DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());
assertThatJson(parsedJson).field("['fullBody']").isEqualTo("{\"foo\":\"bar\",\"baz\":5}");
assertThatJson(parsedJson).field("['authorization']").isEqualTo("secret");
assertThatJson(parsedJson).field("['authorization2']").isEqualTo("secret2");
assertThatJson(parsedJson).field("['path']").isEqualTo("/api/v1/xxxx");
assertThatJson(parsedJson).field("['param']").isEqualTo("bar");
assertThatJson(parsedJson).field("['paramIndex']").isEqualTo("bar2");
assertThatJson(parsedJson).field("['pathIndex']").isEqualTo("v1");
assertThatJson(parsedJson).field("['responseBaz']").isEqualTo(5);
assertThatJson(parsedJson).field("['responseFoo']").isEqualTo("bar");
assertThatJson(parsedJson).field("['url']").isEqualTo("/api/v1/xxxx?foo=bar&foo=bar2");
assertThatJson(parsedJson).field("['responseBaz2']").isEqualTo("Bla bla bar bla bla");
如你所见,来自请求的元素已在响应中得到了正确引用。
生成的 WireMock 存根应类似于以下示例:
{
"request" : {
"urlPath" : "/api/v1/xxxx",
"method" : "POST",
"headers" : {
"Authorization" : {
"equalTo" : "secret2"
}
},
"queryParameters" : {
"foo" : {
"equalTo" : "bar2"
}
},
"bodyPatterns" : [ {
"matchesJsonPath" : "$[?(@.['baz'] == 5)]"
}, {
"matchesJsonPath" : "$[?(@.['foo'] == 'bar')]"
} ]
},
"response" : {
"status" : 200,
"body" : "{\"authorization\":\"{{{request.headers.Authorization.[0]}}}\",\"path\":\"{{{request.path}}}\",\"responseBaz\":{{{jsonpath this '$.baz'}}} ,\"param\":\"{{{request.query.foo.[0]}}}\",\"pathIndex\":\"{{{request.path.[1]}}}\",\"responseBaz2\":\"Bla bla {{{jsonpath this '$.foo'}}} bla bla\",\"responseFoo\":\"{{{jsonpath this '$.foo'}}}\",\"authorization2\":\"{{{request.headers.Authorization.[1]}}}\",\"fullBody\":\"{{{escapejsonbody}}}\",\"url\":\"{{{request.url}}}\",\"paramIndex\":\"{{{request.query.foo.[1]}}}\"}",
"headers" : {
"Authorization" : "{{{request.headers.Authorization.[0]}}};foo"
},
"transformers" : [ "response-template" ]
}
}
发送一个类似于契约的 request
部分中给出的请求,会导致发送以下响应正文:
{
"url" : "/api/v1/xxxx?foo=bar&foo=bar2",
"path" : "/api/v1/xxxx",
"pathIndex" : "v1",
"param" : "bar",
"paramIndex" : "bar2",
"authorization" : "secret",
"authorization2" : "secret2",
"fullBody" : "{\"foo\":\"bar\",\"baz\":5}",
"responseFoo" : "bar",
"responseBaz" : 5,
"responseBaz2" : "Bla bla bar bla bla"
}
此特性仅适用于 WireMock 版本大于或等于 2.5.1。Spring Cloud Contract Verifier 使用 WireMock 的 response-template
响应转换器。它使用 Handlebars 将 Mustache {{{ }}}
模板转换为合适的值。此外,它注册了两个帮助函数:
-
escapejsonbody
:以可嵌入 JSON 的格式对请求正文进行转义。 -
jsonpath
:对于给定的参数,查找请求正文中的对象。
Dynamic Properties in the Matchers Sections
如果您使用 Pact,以下讨论可能看起来很熟悉。很多用户习惯于将合同的动态部分与主体分开设置。
你可以出于两个原因使用 bodyMatchers
部分:
-
定义应该成为存根中的动态值。可以在契约的
request
部分进行设置。 -
验证测试结果。此部分存在于契约的
response
或outputMessage
端。
目前,Spring Cloud Contract Verifier 仅支持基于 JSON 路径的匹配器,具有以下匹配可能性:
Coded DSL
对于存根(在消费者端的测试中):
-
byEquality()
:在提供的 JSON 路径中从使用者请求中获取的值必须等于契约中提供的数值。 -
byRegex(…​)
:在提供的 JSON 路径中从使用者请求中获取的值必须匹配正则表达式。您还可以传递预期匹配值类型(例如asString()
、asLong()
等)。 -
byDate()
:在提供的 JSON 路径中从使用者请求中获取的值必须匹配 ISO Date 值的正则表达式。 -
byTimestamp()
:从消费者请求中采用提供的 JSON 路径中的值的正则表达式必须与 ISO 日期时间值匹配。 -
byTime()
:从消费者请求中采用提供的 JSON 路径中的值的正则表达式必须与 ISO 时间值匹配。
对于验证(在生产者端的生成测试中):
-
byEquality()
:从生产者的响应中采用提供的 JSON 路径中的值必须等于合同中提供的值。 -
byRegex(…​)
:从生产者的响应中采用提供的 JSON 路径中的值必须与正则表达式匹配。 -
byDate()
:从生产者的响应中采用提供的 JSON 路径中的值必须与 ISO 日期值的正则表达式匹配。 -
byTimestamp()
:从生产者的响应中采用提供的 JSON 路径中的值必须与 ISO 日期时间值的正则表达式匹配。 -
byTime()
:从生产者的响应中采用提供的 JSON 路径中的值必须与 ISO 时间值的正则表达式匹配。 -
byType()
:从生产者的响应中采用提供的 JSON 路径中的值需要与合同中响应体中定义的类型相同。byType
可以采用一个闭包,其中可以设置minOccurrence
和maxOccurrence
。对于请求方,应使用闭包断言集合大小。这样,可以断言扁平化集合的大小。要检查非扁平化集合的大小,请使用自定义方法及byCommand(…​)
testMatcher
。 -
byCommand(…​)
:从生产者的响应中采用提供的 JSON 路径中的值作为为用户提供的自定义方法的输入。例如,byCommand('thing($it)')
结果为调用thing
方法,其中与 JSON 路径匹配的值会传递给该方法。从 JSON 中读取对象的类型可以是以下之一,具体取决于 JSON 路径:-
String
:如果指向String
值。 -
JSONArray
:如果指向List
。 -
Map
:如果指向Map
。 -
Number
:如果指向Integer
、Double
或其他类型的数字。 -
Boolean
:如果指向Boolean
。
-
-
byNull()
:从响应中采用提供的 JSON 路径中的值必须为 null。
YAML
请参阅 Groovy 部分,详细了解类型的含义。 |
对于 YAML,匹配器的结构类似于以下示例:
- path: $.thing1
type: by_regex
value: thing2
regexType: as_string
或者,如果您希望使用预定义的正则表达式之一 [only_alpha_unicode, number, any_boolean, ip_address, hostname,
email, url, uuid, iso_date, iso_date_time, iso_time, iso_8601_with_offset, non_empty,
non_blank]
,则可以使用类似于以下示例的内容:
- path: $.thing1
type: by_regex
predefined: only_alpha_unicode
以下列表显示了允许的 type
值列表:
-
For
stubMatchers
:-
by_equality
-
by_regex
-
by_date
-
by_timestamp
-
by_time
-
by_type
-
接受两个附加字段(
minOccurrence
和maxOccurrence
)。
-
-
-
For
testMatchers
:-
by_equality
-
by_regex
-
by_date
-
by_timestamp
-
by_time
-
by_type
-
接受两个附加字段(
minOccurrence
和maxOccurrence
)。
-
-
by_command
-
by_null
-
你还可以定义正则表达式在 regexType
字段中对应于哪种类型。以下列表显示了允许的正则表达式类型:
-
as_integer
-
as_double
-
as_float
-
as_long
-
as_short
-
as_boolean
-
as_string
请考虑以下示例:
Unresolved directive in dsl-dynamic-properties.adoc - include::{verifier_root_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcMethodBodyBuilderWithMatchersSpec.groovy[]
Unresolved directive in dsl-dynamic-properties.adoc - include::{verifier_root_path}/src/test/resources/yml/contract_matchers.yml[]
在前一个示例中,你可以在 matchers
部分看到合同的动态部分。对于请求部分,你可以看到,对于所有字段而非 valueWithoutAMatcher
,存根应当包含的正则表达式的值被明确设置。对于 valueWithoutAMatcher
,验证像不使用匹配器一样进行。在这种情况下,测试执行相等性检查。
对于 bodyMatchers
部分中的响应端,我们以类似的方式定义动态部分。唯一的区别是,还存在 byType
匹配器。验证器引擎检查四个字段,以验证来自测试的响应是否具有 JSON 路径与给定字段匹配、与响应正文中定义的类型相同,以及通过以下检查(基于正在调用的方法)的值:
-
对于
$.valueWithTypeMatch
,引擎将检查类型是否相同。 -
对于
$.valueWithMin
,引擎检查类型并断言大小是否大于或等于最小发生次数。 -
对于
$.valueWithMax
,引擎检查类型并断言大小是否小于或等于最大发生次数。 -
对于
$.valueWithMinMax
,引擎检查类型并声明大小是否在最小和最大出现之间。
结果的测试类似于以下示例(请注意,and
部分将自动生成的断言与来自匹配器的断言分开):
// given:
MockMvcRequestSpecification request = given()
.header("Content-Type", "application/json")
.body("{\"duck\":123,\"alpha\":\"abc\",\"number\":123,\"aBoolean\":true,\"date\":\"2017-01-01\",\"dateTime\":\"2017-01-01T01:23:45\",\"time\":\"01:02:34\",\"valueWithoutAMatcher\":\"foo\",\"valueWithTypeMatch\":\"string\",\"key\":{\"complex.key\":\"foo\"}}");
// when:
ResponseOptions response = given().spec(request)
.get("/get");
// then:
assertThat(response.statusCode()).isEqualTo(200);
assertThat(response.header("Content-Type")).matches("application/json.*");
// and:
DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());
assertThatJson(parsedJson).field("['valueWithoutAMatcher']").isEqualTo("foo");
// and:
assertThat(parsedJson.read("$.duck", String.class)).matches("[0-9]{3}");
assertThat(parsedJson.read("$.duck", Integer.class)).isEqualTo(123);
assertThat(parsedJson.read("$.alpha", String.class)).matches("[\\p{L}]*");
assertThat(parsedJson.read("$.alpha", String.class)).isEqualTo("abc");
assertThat(parsedJson.read("$.number", String.class)).matches("-?(\\d*\\.\\d+|\\d+)");
assertThat(parsedJson.read("$.aBoolean", String.class)).matches("(true|false)");
assertThat(parsedJson.read("$.date", String.class)).matches("(\\d\\d\\d\\d)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])");
assertThat(parsedJson.read("$.dateTime", String.class)).matches("([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])");
assertThat(parsedJson.read("$.time", String.class)).matches("(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])");
assertThat((Object) parsedJson.read("$.valueWithTypeMatch")).isInstanceOf(java.lang.String.class);
assertThat((Object) parsedJson.read("$.valueWithMin")).isInstanceOf(java.util.List.class);
assertThat((java.lang.Iterable) parsedJson.read("$.valueWithMin", java.util.Collection.class)).as("$.valueWithMin").hasSizeGreaterThanOrEqualTo(1);
assertThat((Object) parsedJson.read("$.valueWithMax")).isInstanceOf(java.util.List.class);
assertThat((java.lang.Iterable) parsedJson.read("$.valueWithMax", java.util.Collection.class)).as("$.valueWithMax").hasSizeLessThanOrEqualTo(3);
assertThat((Object) parsedJson.read("$.valueWithMinMax")).isInstanceOf(java.util.List.class);
assertThat((java.lang.Iterable) parsedJson.read("$.valueWithMinMax", java.util.Collection.class)).as("$.valueWithMinMax").hasSizeBetween(1, 3);
assertThat((Object) parsedJson.read("$.valueWithMinEmpty")).isInstanceOf(java.util.List.class);
assertThat((java.lang.Iterable) parsedJson.read("$.valueWithMinEmpty", java.util.Collection.class)).as("$.valueWithMinEmpty").hasSizeGreaterThanOrEqualTo(0);
assertThat((Object) parsedJson.read("$.valueWithMaxEmpty")).isInstanceOf(java.util.List.class);
assertThat((java.lang.Iterable) parsedJson.read("$.valueWithMaxEmpty", java.util.Collection.class)).as("$.valueWithMaxEmpty").hasSizeLessThanOrEqualTo(0);
assertThatValueIsANumber(parsedJson.read("$.duck"));
assertThat(parsedJson.read("$.['key'].['complex.key']", String.class)).isEqualTo("foo");
请注意,对于 byCommand
方法,示例调用了 assertThatValueIsANumber
。该方法必须在测试基类中定义,或最好静态导入到您的测试中。请注意,byCommand
调用已转换为 assertThatValueIsANumber(parsedJson.read("$.duck"));
。这意味着引擎采用了方法名,并将合适的 JSON 路径作为参数传递给了它。
结果的 WireMock 存根位于以下示例中:
Unresolved directive in dsl-dynamic-properties.adoc - include::{plugins_path}/spring-cloud-contract-converters/src/test/groovy/org/springframework/cloud/contract/verifier/wiremock/DslToWireMockClientConverterSpec.groovy[]
如果您使用 matcher
,则 matcher
通过 JSON 路径寻址的请求和响应部分将从断言中移除。在验证集合时,必须为集合的 all 元素创建匹配器。
请考虑以下示例:
Contract.make {
request {
method 'GET'
url("/foo")
}
response {
status OK()
body(events: [[
operation : 'EXPORT',
eventId : '16f1ed75-0bcc-4f0d-a04d-3121798faf99',
status : 'OK'
], [
operation : 'INPUT_PROCESSING',
eventId : '3bb4ac82-6652-462f-b6d1-75e424a0024a',
status : 'OK'
]
]
)
bodyMatchers {
jsonPath('$.events[0].operation', byRegex('.+'))
jsonPath('$.events[0].eventId', byRegex('^([a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})$'))
jsonPath('$.events[0].status', byRegex('.+'))
}
}
}
前面的代码导致创建以下测试(代码块仅显示断言部分):
and:
DocumentContext parsedJson = JsonPath.parse(response.body.asString())
assertThatJson(parsedJson).array("['events']").contains("['eventId']").isEqualTo("16f1ed75-0bcc-4f0d-a04d-3121798faf99")
assertThatJson(parsedJson).array("['events']").contains("['operation']").isEqualTo("EXPORT")
assertThatJson(parsedJson).array("['events']").contains("['operation']").isEqualTo("INPUT_PROCESSING")
assertThatJson(parsedJson).array("['events']").contains("['eventId']").isEqualTo("3bb4ac82-6652-462f-b6d1-75e424a0024a")
assertThatJson(parsedJson).array("['events']").contains("['status']").isEqualTo("OK")
and:
assertThat(parsedJson.read("\$.events[0].operation", String.class)).matches(".+")
assertThat(parsedJson.read("\$.events[0].eventId", String.class)).matches("^([a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})\$")
assertThat(parsedJson.read("\$.events[0].status", String.class)).matches(".+")
请注意,断言是错误的。仅对数组的第一个元素进行了断言。要修复此问题,请将断言应用于整个 $.events
集合,并使用 byCommand(…)
方法对其进行断言。