W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
如果您使用Pact,那么以下討論可能看起來很熟悉。很少有用戶習慣于在主體之間進行分隔并設(shè)置合同的動態(tài)部分。
您可以使用bodyMatchers
部分有兩個原因:
request
或inputMessage
部分中進行設(shè)置。response
或outputMessage
中。當前,Spring Cloud Contract驗證程序僅支持具有以下匹配可能性的基于JSON路徑的匹配器:
Groovy DSL
對于存根(在消費者方面的測試中):
byEquality()
:通過提供的JSON路徑從消費者請求中獲取的值必須等于合同中提供的值。byRegex(…?)
:通過提供的JSON路徑從消費者請求中獲取的值必須與正則表達式匹配。您還可以傳遞期望的匹配值的類型(例如asString()
,asLong()
等)byDate()
:通過提供的JSON路徑從消費者請求中獲取的值必須與ISO日期值的正則表達式匹配。byTimestamp()
:通過提供的JSON路徑從消費者請求中獲取的值必須與ISO DateTime值的正則表達式匹配。byTime()
:通過提供的JSON路徑從消費者請求中獲取的值必須與ISO時間值的正則表達式匹配。進行驗證(在生產(chǎn)者方生成的測試中):
byEquality()
:通過提供的JSON路徑從生產(chǎn)者的響應(yīng)中獲取的值必須等于合同中提供的值。byRegex(…?)
:通過提供的JSON路徑從生產(chǎn)者的響應(yīng)中獲取的值必須與正則表達式匹配。byDate()
:通過提供的JSON路徑從生產(chǎn)者的響應(yīng)中獲取的值必須與ISO日期值的正則表達式匹配。byTimestamp()
:通過提供的JSON路徑從生產(chǎn)者的響應(yīng)中獲取的值必須與ISO DateTime值的正則表達式匹配。byTime()
:通過提供的JSON路徑從生產(chǎn)者的響應(yīng)中獲取的值必須與ISO時間值的正則表達式匹配。byType()
:通過提供的JSON路徑從生產(chǎn)者的響應(yīng)中獲取的值必須與合同中的響應(yīng)主體中定義的類型相同。byType
可以關(guān)閉,您可以在其中設(shè)置minOccurrence
和maxOccurrence
。對于請求端,應(yīng)該使用閉包聲明集合的大小。這樣,您可以聲明展平集合的大小。要檢查未展平的集合的大小,請對byCommand(…?)
testMatcher使用自定義方法。byCommand(…?)
:通過提供的JSON路徑從生產(chǎn)者的響應(yīng)中獲取的值作為輸入傳遞到您提供的自定義方法。例如,byCommand('foo($it)')
導致調(diào)用foo
方法,與JSON路徑匹配的值將傳遞到該方法。從JSON讀取的對象的類型可以是以下之一,具體取決于JSON路徑:
String
:如果您指向String
值。JSONArray
:如果指向List
。Map
:如果指向Map
。Number
:如果指向Integer
,Double
或其他類型的數(shù)字。Boolean
:如果指向Boolean
。byNull()
:通過提供的JSON路徑從響應(yīng)中獲取的值必須為nullYAML。 請閱讀Groovy部分,詳細了解類型的含義
對于YAML,匹配器的結(jié)構(gòu)如下所示
- path: $.foo type: by_regex value: bar 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: $.foo type: by_regex predefined: only_alpha_unicode
在下面,您可以找到允許的“類型”列表。
對于stubMatchers
:
by_equality
by_regex
by_date
by_timestamp
by_time
by_type
minOccurrence
和maxOccurrence
。對于testMatchers
:
by_equality
by_regex
by_date
by_timestamp
by_time
by_type
minOccurrence
和maxOccurrence
。by_command
by_null
您還可以通過regexType
字段定義正則表達式對應(yīng)的類型。在下面,您可以找到允許的正則表達式類型列表:
考慮以下示例:
Groovy DSL。
Contract contractDsl = Contract.make { request { method 'GET' urlPath '/get' 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' ] ]) bodyMatchers { jsonPath('$.duck', byRegex("[0-9]{3}").asInteger()) jsonPath('$.duck', byEquality()) jsonPath('$.alpha', byRegex(onlyAlphaUnicode()).asString()) jsonPath('$.alpha', byEquality()) jsonPath('$.number', byRegex(number()).asInteger()) jsonPath('$.aBoolean', byRegex(anyBoolean()).asBooleanType()) jsonPath('$.date', byDate()) jsonPath('$.dateTime', byTimestamp()) jsonPath('$.time', byTime()) jsonPath("\$.['key'].['complex.key']", byEquality()) } headers { contentType(applicationJson()) } } response { status OK() body([ duck : 123, alpha : 'abc', number : 123, positiveInteger : 1234567890, negativeInteger : -1234567890, positiveDecimalNumber: 123.4567890, negativeDecimalNumber: -123.4567890, aBoolean : true, date : '2017-01-01', dateTime : '2017-01-01T01:23:45', time : "01:02:34", valueWithoutAMatcher : 'foo', valueWithTypeMatch : 'string', valueWithMin : [ 1, 2, 3 ], valueWithMax : [ 1, 2, 3 ], valueWithMinMax : [ 1, 2, 3 ], valueWithMinEmpty : [], valueWithMaxEmpty : [], key : [ 'complex.key': 'foo' ], nullValue : null ]) bodyMatchers { // asserts the jsonpath value against manual regex jsonPath('$.duck', byRegex("[0-9]{3}").asInteger()) // asserts the jsonpath value against the provided value jsonPath('$.duck', byEquality()) // asserts the jsonpath value against some default regex jsonPath('$.alpha', byRegex(onlyAlphaUnicode()).asString()) jsonPath('$.alpha', byEquality()) jsonPath('$.number', byRegex(number()).asInteger()) jsonPath('$.positiveInteger', byRegex(anInteger()).asInteger()) jsonPath('$.negativeInteger', byRegex(anInteger()).asInteger()) jsonPath('$.positiveDecimalNumber', byRegex(aDouble()).asDouble()) jsonPath('$.negativeDecimalNumber', byRegex(aDouble()).asDouble()) jsonPath('$.aBoolean', byRegex(anyBoolean()).asBooleanType()) // asserts vs inbuilt time related regex jsonPath('$.date', byDate()) jsonPath('$.dateTime', byTimestamp()) jsonPath('$.time', byTime()) // asserts that the resulting type is the same as in response body jsonPath('$.valueWithTypeMatch', byType()) jsonPath('$.valueWithMin', byType { // results in verification of size of array (min 1) minOccurrence(1) }) jsonPath('$.valueWithMax', byType { // results in verification of size of array (max 3) maxOccurrence(3) }) jsonPath('$.valueWithMinMax', byType { // results in verification of size of array (min 1 & max 3) minOccurrence(1) maxOccurrence(3) }) jsonPath('$.valueWithMinEmpty', byType { // results in verification of size of array (min 0) minOccurrence(0) }) jsonPath('$.valueWithMaxEmpty', byType { // results in verification of size of array (max 0) maxOccurrence(0) }) // will execute a method `assertThatValueIsANumber` jsonPath('$.duck', byCommand('assertThatValueIsANumber($it)')) jsonPath("\$.['key'].['complex.key']", byEquality()) jsonPath('$.nullValue', byNull()) } headers { contentType(applicationJson()) header('Some-Header', $(c('someValue'), p(regex('[a-zA-Z]{9}')))) } } }
YAML。
request: method: GET urlPath: /get/1 headers: Content-Type: application/json cookies: foo: 2 bar: 3 queryParameters: limit: 10 offset: 20 filter: 'email' sort: name search: 55 age: 99 name: John.Doe email: 'bob@email.com' 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' nullValue: null valueWithMin: - 1 - 2 - 3 valueWithMax: - 1 - 2 - 3 valueWithMinMax: - 1 - 2 - 3 valueWithMinEmpty: [] valueWithMaxEmpty: [] matchers: url: regex: /get/[0-9] # predefined: # execute a method #command: 'equals($it)' queryParameters: - key: limit type: equal_to value: 20 - key: offset type: containing value: 20 - key: sort type: equal_to value: name - key: search type: not_matching value: '^[0-9]{2}$' - key: age type: not_matching value: '^\\w*$' - key: name type: matching value: 'John.*' - key: hello type: absent cookies: - key: foo regex: '[0-9]' - key: bar command: 'equals($it)' headers: - key: Content-Type regex: "application/json.*" body: - path: $.duck type: by_regex value: "[0-9]{3}" - path: $.duck type: by_equality - path: $.alpha type: by_regex predefined: only_alpha_unicode - path: $.alpha type: by_equality - path: $.number type: by_regex predefined: number - path: $.aBoolean type: by_regex predefined: any_boolean - path: $.date type: by_date - path: $.dateTime type: by_timestamp - path: $.time type: by_time - path: "$.['key'].['complex.key']" type: by_equality - path: $.nullvalue type: by_null - path: $.valueWithMin type: by_type minOccurrence: 1 - path: $.valueWithMax type: by_type maxOccurrence: 3 - path: $.valueWithMinMax type: by_type minOccurrence: 1 maxOccurrence: 3 response: status: 200 cookies: foo: 1 bar: 2 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" valueWithMin: - 1 - 2 - 3 valueWithMax: - 1 - 2 - 3 valueWithMinMax: - 1 - 2 - 3 valueWithMinEmpty: [] valueWithMaxEmpty: [] key: 'complex.key': 'foo' nulValue: null matchers: headers: - key: Content-Type regex: "application/json.*" cookies: - key: foo regex: '[0-9]' - key: bar command: 'equals($it)' body: - path: $.duck type: by_regex value: "[0-9]{3}" - path: $.duck type: by_equality - path: $.alpha type: by_regex predefined: only_alpha_unicode - path: $.alpha type: by_equality - path: $.number type: by_regex predefined: number - path: $.aBoolean type: by_regex predefined: any_boolean - path: $.date type: by_date - path: $.dateTime type: by_timestamp - path: $.time type: by_time - path: $.valueWithTypeMatch type: by_type - path: $.valueWithMin type: by_type minOccurrence: 1 - path: $.valueWithMax type: by_type maxOccurrence: 3 - path: $.valueWithMinMax type: by_type minOccurrence: 1 maxOccurrence: 3 - path: $.valueWithMinEmpty type: by_type minOccurrence: 0 - path: $.valueWithMaxEmpty type: by_type maxOccurrence: 0 - path: $.duck type: by_command value: assertThatValueIsANumber($it) - path: $.nullValue type: by_null value: null headers: Content-Type: application/json
在前面的示例中,您可以在matchers
部分中查看合同的動態(tài)部分。對于請求部分,您可以看到,對于valueWithoutAMatcher
以外的所有字段,存根都應(yīng)包含的正則表達式的值已明確設(shè)置。對于valueWithoutAMatcher
,驗證的方式與不使用匹配器的方式相同。在這種情況下,測試將執(zhí)行相等性檢查。
對于bodyMatchers
部分中的響應(yīng)端,我們以類似的方式定義動態(tài)部分。唯一的區(qū)別是byType
匹配器也存在。驗證程序引擎檢查四個字段,以驗證來自測試的響應(yīng)是否具有與JSON路徑匹配給定字段的值,與響應(yīng)主體中定義的類型相同的類型,并通過以下檢查(基于方法被調(diào)用):
$.valueWithTypeMatch
,引擎檢查類型是否相同。$.valueWithMin
,引擎檢查類型并斷言大小是否大于或等于最小出現(xiàn)次數(shù)。$.valueWithMax
,引擎檢查類型并斷言大小是否小于或等于最大出現(xiàn)次數(shù)。$.valueWithMinMax
,引擎檢查類型并斷言大小是否在最小和最大出現(xiàn)之間。生成的測試類似于以下示例(請注意,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
方法,該示例調(diào)用assertThatValueIsANumber
。此方法必須在測試基類中定義或靜態(tài)導入到測試中。請注意,byCommand
調(diào)用已轉(zhuǎn)換為assertThatValueIsANumber(parsedJson.read("$.duck"));
。這意味著引擎采用了方法名稱,并將正確的JSON路徑作為參數(shù)傳遞給它。
在下面的示例中,生成的WireMock存根為:
''' { "request" : { "urlPath" : "/get", "method" : "POST", "headers" : { "Content-Type" : { "matches" : "application/json.*" } }, "bodyPatterns" : [ { "matchesJsonPath" : "$.['list'].['some'].['nested'][?(@.['anothervalue'] == 4)]" }, { "matchesJsonPath" : "$[?(@.['valueWithoutAMatcher'] == 'foo')]" }, { "matchesJsonPath" : "$[?(@.['valueWithTypeMatch'] == 'string')]" }, { "matchesJsonPath" : "$.['list'].['someother'].['nested'][?(@.['json'] == 'with value')]" }, { "matchesJsonPath" : "$.['list'].['someother'].['nested'][?(@.['anothervalue'] == 4)]" }, { "matchesJsonPath" : "$[?(@.duck =~ /([0-9]{3})/)]" }, { "matchesJsonPath" : "$[?(@.duck == 123)]" }, { "matchesJsonPath" : "$[?(@.alpha =~ /([\\\\p{L}]*)/)]" }, { "matchesJsonPath" : "$[?(@.alpha == 'abc')]" }, { "matchesJsonPath" : "$[?(@.number =~ /(-?(\\\\d*\\\\.\\\\d+|\\\\d+))/)]" }, { "matchesJsonPath" : "$[?(@.aBoolean =~ /((true|false))/)]" }, { "matchesJsonPath" : "$[?(@.date =~ /((\\\\d\\\\d\\\\d\\\\d)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01]))/)]" }, { "matchesJsonPath" : "$[?(@.dateTime =~ /(([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]))/)]" }, { "matchesJsonPath" : "$[?(@.time =~ /((2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9]))/)]" }, { "matchesJsonPath" : "$.list.some.nested[?(@.json =~ /(.*)/)]" }, { "matchesJsonPath" : "$[?(@.valueWithMin.size() >= 1)]" }, { "matchesJsonPath" : "$[?(@.valueWithMax.size() <= 3)]" }, { "matchesJsonPath" : "$[?(@.valueWithMinMax.size() >= 1 && @.valueWithMinMax.size() <= 3)]" }, { "matchesJsonPath" : "$[?(@.valueWithOccurrence.size() >= 4 && @.valueWithOccurrence.size() <= 4)]" } ] }, "response" : { "status" : 200, "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\\",\\"valueWithMin\\":[1,2,3],\\"valueWithMax\\":[1,2,3],\\"valueWithMinMax\\":[1,2,3],\\"valueWithOccurrence\\":[1,2,3,4]}", "headers" : { "Content-Type" : "application/json" }, "transformers" : [ "response-template" ] } } '''
如果您使用
matcher
,則matcher
用JSON路徑尋址的請求和響應(yīng)部分將從聲明中刪除。在驗證集合的情況下,必須為集合的所有元素創(chuàng)建匹配器。
考慮以下示例:
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('.+')) } } }
前面的代碼導致創(chuàng)建以下測試(代碼塊僅顯示斷言部分):
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(".+")
如您所見,斷言的格式不正確。僅聲明數(shù)組的第一個元素。為了解決此問題,您應(yīng)該將斷言應(yīng)用于整個$.events
集合,并使用byCommand(…?)
方法進行斷言。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: