diff --git a/multi/multi__contract_dsl.html b/multi/multi__contract_dsl.html index e0a65c04ac..c09e61342f 100644 --- a/multi/multi__contract_dsl.html +++ b/multi/multi__contract_dsl.html @@ -1,15 +1,14 @@
-![]() | Important | |||||
|---|---|---|---|---|---|---|
Remember that, inside the contract file, you have to provide the fully + Spring Cloud Contract supports out of the box 2 types of DSL. One written in
+ If you decide to write the contract in Groovy, do not be alarmed if you have not used Groovy +before. Knowledge of the language is not really needed, as the Contract DSL uses only a +tiny subset of it (only literals, method calls and closures). Also, the DSL is statically +typed, to make it programmer-readable without any knowledge of the DSL itself.
Contract DSL is written in Groovy, but do not be alarmed if you have not used Groovy -before. Knowledge of the language is not really needed, as the Contract DSL uses only a -tiny subset of it (only literals, method calls and closures). Also, the DSL is statically -typed, to make it programmer-readable without any knowledge of the DSL itself.
The Contract is present in the The following is a complete example of a contract definition: org.springframework.cloud.contract.spec.Contract.make {
+ |
![]() | Tip |
|---|---|
Spring Cloud Contract supports defining multiple contracts in a single file. |
The following is a complete example of a Groovy contract definition:
org.springframework.cloud.contract.spec.Contract.make {
request {
method 'PUT'
url '/api/12'
@@ -50,16 +49,60 @@ Cloud Contract Verifier repository.The following is a complete exampl
response {
status 200
}
-}
![]() | Note |
|---|---|
The preceding example does not contain all the features of the DSL appear. The -remainder of this section describes the other features. |
You can compile Contracts to WireMock stubs mapping using standalone maven command:
-mvn org.springframework.cloud:spring-cloud-contract-maven-plugin:convert
![]() | Warning | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Spring Cloud Contract Verifier does not properly support XML. Please use JSON or +} The following is a complete example of a YAML contract definition: description: Some description +name: some name +priority: 8 +ignored: true +request: + url: /foo + queryParameters: + a: b + b: c + method: PUT + headers: + foo: bar + fooReq: baz + body: + foo: bar + matchers: + body: + - path: $.foo + type: by_regex + value: bar + headers: + - key: foo + regex: bar +response: + status: 200 + headers: + foo2: bar + foo3: foo33 + fooRes: baz + body: + foo2: bar + foo3: baz + matchers: + body: + - path: $.foo2 + type: by_regex + value: bar + - path: $.foo3 + type: by_command + value: executeMe($it) + headers: + - key: foo2 + regex: bar + - key: foo3 + command: andMeToo($it)
The following sections describe the most common top-level elements: You can add a org.springframework.cloud.contract.spec.Contract.make {
+following code shows an example:You can provide a name for your contract. Assume that you provided the following name: + } + YAML. + description: Some description +name: some name +priority: 8 +ignored: true +request: + url: /foo + queryParameters: + a: b + b: c + method: PUT + headers: + foo: bar + fooReq: baz + body: + foo: bar + matchers: + body: + - path: $.foo + type: by_regex + value: bar + headers: + - key: foo + regex: bar +response: + status: 200 + headers: + foo2: bar + foo3: foo33 + fooRes: baz + body: + foo2: bar + foo3: baz + matchers: + body: + - path: $.foo2 + type: by_regex + value: bar + - path: $.foo3 + type: by_command + value: executeMe($it) + headers: + - key: foo2 + regex: bar + - key: foo3 + command: andMeToo($it) + You can provide a name for your contract. Assume that you provided the following name:
|
Groovy DSL. +
org.springframework.cloud.contract.spec.Contract.make {
+ name("some_special_name")
+}+
YAML. +
name: some name
+
If you want to ignore a contract, you can either set a value of ignored contracts in the
+plugin configuration or set the ignored property on the contract itself:
Groovy DSL. +
org.springframework.cloud.contract.spec.Contract.make {
ignored()
-}Starting with version 1.2.0, you can pass values from files. Assume that you have the
+}
+
YAML. +
ignored: true
+
Starting with version 1.2.0, you can pass values from files. Assume that you have the
following resources in our project.
└── src
└── test
└── resources
└── contracts
├── readFromFile.groovy
├── request.json
- └── response.jsonFurther assume that your contract is as follows:
import org.springframework.cloud.contract.spec.Contract
+ └── response.jsonFurther assume that your contract is as follows:
Groovy DSL. +
import org.springframework.cloud.contract.spec.Contract
Contract.make {
request {
@@ -102,11 +203,20 @@ Contract.make {
contentType(textPlain())
}
}
-}Further assume that the JSON files is as follows:
request.json
{ "status" : "REQUEST" }
response.json
{ "status" : "RESPONSE" }
When test or stub generation takes place, the contents of the file is passed to the body
-of a request or a response. That works because of the file(…) method. The argument of
-that method needs to be a file with location relative to the folder in which the contract
-lays.
The following methods can be called in the top-level closure of a contract definition.
-request and response are mandatory. priority is optional.
org.springframework.cloud.contract.spec.Contract.make {
+}+
YAML. +
request: + method: GET + url: /foo + bodyFromFile: request.json +response: + status: 200 + bodyFromFile: response.json
+
Further assume that the JSON files is as follows:
request.json
{ "status" : "REQUEST" }
response.json
{ "status" : "RESPONSE" }
When test or stub generation takes place, the contents of the file is passed to the body +of a request or a response. The name of the file needs to be a file with location +relative to the folder in which the contract lays.
The following methods can be called in the top-level closure of a contract definition.
+request and response are mandatory. priority is optional.
Groovy DSL. +
org.springframework.cloud.contract.spec.Contract.make {
// Definition of HTTP request part of the contract
// (this can be a valid request or invalid depending
// on type of contract being specified).
@@ -125,8 +235,18 @@ lays.The HTTP protocol requires only method and address to be specified in a request. The -same information is mandatory in request definition of the Contract.
org.springframework.cloud.contract.spec.Contract.make {
+}+
YAML. +
priority: 8 +request: +... +response: +...
+
![]() | Important |
|---|---|
If you want to make your contract have a higher value of priority
+you need to pass a lower number to the |
The HTTP protocol requires only method and url to be specified in a request. The +same information is mandatory in request definition of the Contract.
Groovy DSL. +
org.springframework.cloud.contract.spec.Contract.make {
request {
// HTTP request method (GET/POST/PUT/DELETE).
method 'GET'
@@ -138,8 +258,13 @@ same information is mandatory in request definition of the Contract.//...
}
-}
It is possible to specify an absolute rather than relative url, but using urlPath is
-the recommended way, as doing so makes the tests host-independent.
org.springframework.cloud.contract.spec.Contract.make {
+}
+
YAML.
+
method: PUT
+url: /foo
+
It is possible to specify an absolute rather than relative url, but using urlPath is
+the recommended way, as doing so makes the tests host-independent.
Groovy DSL.
+
org.springframework.cloud.contract.spec.Contract.make {
request {
method 'GET'
@@ -150,8 +275,13 @@ the recommended way, as doing so makes the tests ho
response {
//...
}
-}request may contain query parameters, which are specified in a closure nested in a
-call to urlPath or url.
org.springframework.cloud.contract.spec.Contract.make {
+}
+
YAML.
+
request:
+ method: PUT
+ urlPath: /foo
+
request may contain query parameters.
Groovy DSL.
+
org.springframework.cloud.contract.spec.Contract.make {
request {
//...
@@ -191,7 +321,50 @@ call to urlPath or url
response {
//...
}
-}request may contain additional request headers, as shown in the following example:
org.springframework.cloud.contract.spec.Contract.make {
+}
+
YAML.
+
request:
+...
+ queryParameters:
+ a: b
+ b: c
+ headers:
+ foo: bar
+ fooReq: baz
+ body:
+ foo: bar
+ matchers:
+ body:
+ - path: $.foo
+ type: by_regex
+ value: bar
+ headers:
+ - key: foo
+ regex: bar
+response:
+ status: 200
+ headers:
+ foo2: bar
+ foo3: foo33
+ fooRes: baz
+ body:
+ foo2: bar
+ foo3: baz
+ matchers:
+ body:
+ - path: $.foo2
+ type: by_regex
+ value: bar
+ - path: $.foo3
+ type: by_command
+ value: executeMe($it)
+ headers:
+ - key: foo2
+ regex: bar
+ - key: foo3
+ command: andMeToo($it)
+
request may contain additional request headers, as shown in the following example:
Groovy DSL.
+
org.springframework.cloud.contract.spec.Contract.make {
request {
//...
@@ -208,7 +381,15 @@ call to urlPath or url
response {
//...
}
-}request may contain a request body, as shown in the following example:
org.springframework.cloud.contract.spec.Contract.make {
+}
+
YAML.
+
request:
+...
+headers:
+ foo: bar
+ fooReq: baz
+
request may contain a request body:
Groovy DSL.
+
org.springframework.cloud.contract.spec.Contract.make {
request {
//...
@@ -220,8 +401,15 @@ call to urlPath or url
response {
//...
}
-}request may contain multipart elements. To include multipart elements, call the
-multipart() method, as shown in the following example
org.springframework.cloud.contract.spec.Contract contractDsl = org.springframework.cloud.contract.spec.Contract.make {
+}
+
YAML.
+
request:
+...
+body:
+ foo: bar
+
request may contain multipart elements. To include multipart elements, use the
+multipart method/section, as shown in the following examples
Groovy DSL.
+
org.springframework.cloud.contract.spec.Contract contractDsl = org.springframework.cloud.contract.spec.Contract.make {
request {
method "PUT"
url "/multipart"
@@ -244,11 +432,49 @@ call to urlPath or url
response {
status 200
}
-}In the preceding example, we define parameters in either of two ways:
- Directly, by using the map notation, where the value can be a dynamic property (such as
+}
+
YAML.
+
request:
+ method: PUT
+ url: /multipart
+ headers:
+ Content-Type: multipart/form-data;boundary=AaB03x
+ multipart:
+ params:
+ # key (parameter name), value (parameter value) pair
+ formParameter: '"formParameterValue"'
+ someBooleanParameter: true
+ named:
+ - paramName: file
+ fileName: filename.csv
+ fileContent: file content
+ matchers:
+ multipart:
+ params:
+ - key: formParameter
+ regex: ".+"
+ - key: someBooleanParameter
+ predefined: any_boolean
+ named:
+ - paramName: file
+ fileName:
+ predefined: non_empty
+ fileContent:
+ predefined: non_empty
+response:
+ status: 200
+
In the preceding example, we define parameters in either of two ways:
Groovy 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 via a method with two arguments,
such as named("fileName", "fileContent"), or via a map notation, such as
-named(name: "fileName", content: "fileContent").
From this contract, the generated test is as follows:
// given:
+named(name: "fileName", content: "fileContent").
YAML
- The multipart parameters are set via
multipart.params section - The named parameters (the
fileName and fileContent for a given parameter name)
+can be set via the multipart.named section. That section contains
+the paramName (name of the parameter), fileName (name of the file),
+fileContent (content of the file) fields The dynamic bits can be set via the matchers.multipart section
- for parameters use the
params section that can accept
+regex or a predefined regular expression - for named params use the
named section where first you
+define the parameter name via paramName and then you can pass the
+parametrization of either fileName or fileContent via
+regex or a predefined regular expression
From this contract, the generated test is as follows:
// given:
MockMvcRequestSpecification request = given()
.header("Content-Type", "multipart/form-data;boundary=AaB03x")
.param("formParameter", "\"formParameterValue\"")
@@ -284,7 +510,8 @@ such as named("fileName", "fileContent"), or via a
}
}
'''
The response must contain an HTTP status code and may contain other information. The -following code shows an example:
org.springframework.cloud.contract.spec.Contract.make {
+following code shows an example:Groovy DSL.
+
org.springframework.cloud.contract.spec.Contract.make {
request {
//...
}
@@ -293,12 +520,18 @@ following code shows an example:org.springframew
// in response to request specified above.
status 200
}
-}
Besides status, the response may contain headers and a body, both of which are
+}
+
YAML.
+
response:
+...
+status: 200
+
Besides status, the response may contain headers and a body, both of which are
specified the same way as in the request (see the previous paragraph).
The contract can contain some dynamic properties: timestamps, IDs, and so on. You do not want to force the consumers to stub their clocks to always return the same value of time -so that it gets matched by the stub. You can provide the dynamic parts in your contracts +so that it gets matched by the stub.
For Groovy DSL you can provide the dynamic parts in your contracts
in two ways: pass them directly in the body or set them in separate sections called
-testMatchers and stubMatchers.
You can set the properties inside the body either with the value method or, if you use
+testMatchers and stubMatchers.
For YAML you can only use the matchers section.
![]() | Important |
|---|---|
This section is valid only for Groovy DSL. Check out the +Section 8.5.7, “Dynamic Properties in the Matchers Sections” section for YAML examples of a similar feature. |
You can set the properties inside the body either with the value method or, if you use
the Groovy map notation, with $(). The following example shows how to set dynamic
properties with the value method:
value(consumer(...), producer(...)) value(c(...), p(...)) @@ -307,7 +540,8 @@ value(client(...), server(...))
The following example shows how to set d $(c(...), p(...)) $(stub(...), test(...)) $(client(...), server(...))
Both approaches work equally well. stub and client methods are aliases over the consumer
-method. Subsequent sections take a closer look at what you can do with those values.
You can use regular expressions to write your requests in Contract DSL. Doing so is +method. Subsequent sections take a closer look at what you can do with those values.
![]() | Important |
|---|---|
This section is valid only for Groovy DSL. Check out the +Section 8.5.7, “Dynamic Properties in the Matchers Sections” section for YAML examples of a similar feature. |
You can use regular expressions to write your requests in Contract DSL. Doing so is particularly useful when you want to indicate that a given response should be provided for requests that follow a given pattern. Also, you can use regular expressions when you need to use patterns and not exact values both for your test and your server side tests.
The following example shows how to use regular expressions to write a request:
org.springframework.cloud.contract.spec.Contract.make {
@@ -453,7 +687,8 @@ Pattern nonBlank() {
message: "User not found by email = [${value(producer(regex(email())), consumer('not.existing@user.com'))}]"
)
}
-}It is possible to provide optional parameters in your contract. However, you can provide +}
![]() | Important |
|---|---|
This section is valid only for Groovy DSL. Check out the +Section 8.5.7, “Dynamic Properties in the Matchers Sections” section for YAML examples of a similar feature. |
It is possible to provide optional parameters in your contract. However, you can provide optional parameters only for the following:
The following example shows how to provide optional parameters:
org.springframework.cloud.contract.spec.Contract.make {
priority 1
request {
@@ -496,29 +731,30 @@ expression that must be present 0 or more times.If you use Spock for, the
"""
The following stub would also be generated:
''' { "request" : { - "url" : "/users/password", - "method" : "POST", - "bodyPatterns" : [ { - "matchesJsonPath" : "$[?(@.['email'] =~ /([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,6})?/)]" - }, { - "matchesJsonPath" : "$[?(@.['callback_url'] =~ /((http[s]?|ftp):\\\\/)\\\\/?([^:\\\\/\\\\s]+)(:[0-9]{1,5})?/)]" - } ], - "headers" : { - "Content-Type" : { - "equalTo" : "application/json" - } - } + "url" : "/users/password", + "method" : "POST", + "bodyPatterns" : [ { + "matchesJsonPath" : "$[?(@.['email'] =~ /([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,6})?/)]" + }, { + "matchesJsonPath" : "$[?(@.['callback_url'] =~ /((http[s]?|ftp):\\\\/)\\\\/?([^:\\\\/\\\\s]+)(:[0-9]{1,5})?/)]" + } ], + "headers" : { + "Content-Type" : { + "equalTo" : "application/json" + } + } }, "response" : { - "status" : 404, - "body" : "{\\"code\\":\\"123123\\",\\"message\\":\\"User not found by email == [not.existing@user.com]\\"}", - "headers" : { - "Content-Type" : "application/json" - } + "status" : 404, + "body" : "{\\"code\\":\\"123123\\",\\"message\\":\\"User not found by email == [not.existing@user.com]\\"}", + "headers" : { + "Content-Type" : "application/json" + } }, "priority" : 1 } -'''
You can define a method call that executes on the server side during the test. Such a +'''
![]() | Important |
|---|---|
This section is valid only for Groovy DSL. Check out the +Section 8.5.7, “Dynamic Properties in the Matchers Sections” section for YAML examples of a similar feature. |
You can define a method call that executes on the server side during the test. Such a method can be added to the class defined as "baseClassForTests" in the configuration. The following code shows an example of the contract portion of the test case:
org.springframework.cloud.contract.spec.Contract.make {
request {
@@ -582,11 +818,17 @@ It should resemble the following code:// then:
assertThat(response.statusCode()).isEqualTo(200);
The best situation is to provide fixed values, but sometimes you need to reference a
-request in your response. To do so, you can use the fromRequest() method, which lets
+request in your response.
If you’re writing contracts using Groovy DSL, you can use the fromRequest() method, which lets
you reference a bunch of elements from the HTTP request. You can use the following
options:
fromRequest().url(): Returns the request URL and query parameters.fromRequest().query(String key): Returns the first query parameter with a given name.fromRequest().query(String key, int index): Returns the nth query parameter with a
given name.fromRequest().path(): Returns the full path.fromRequest().path(int index): Returns the nth path element.fromRequest().header(String key): Returns the first header with a given name.fromRequest().header(String key, int index): Returns the nth header with a given name.fromRequest().body(): Returns the full request body.fromRequest().body(String jsonPath): Returns the element from the request that
-matches the JSON Path.Consider the following contract:
Contract contractDsl = Contract.make {
+matches the JSON Path.If you’re using the YAML contract definition you have to use the
+Handlebars {{{ }}} notation with custom, Spring Cloud Contract
+ functions to achieve this.
{{{ request.url }}}: Returns the request URL and query parameters.{{{ request.query.key.[index] }}}: Returns the nth query parameter with a given name.
+E.g. for key foo, first entry {{{ request.query.foo.[0] }}}{{{ request.path }}}: Returns the full path.{{{ request.path.[index] }}}: Returns the nth path element. E.g.
+for first entry `{{{ request.path.[0] }}}{{{ request.headers.key }}}: Returns the first header with a given name.{{{ request.headers.key.[index] }}}: Returns the nth header with a given name.{{{ request.body }}}: Returns the full request body.{{{ jsonpath this 'your.json.path' }}}: Returns the element from the request that
+matches the JSON Path. E.g. for json path $.foo - {{{ jsonpath this '$.foo' }}}Consider the following contract:
Groovy DSL. +
Contract contractDsl = Contract.make {
request {
method 'GET'
url('/api/v1/xxxx') {
@@ -620,7 +862,39 @@ matches the JSON Path.Consider the following contract:
Running a JUnit test generation leads to a test that resembles the following example:
// given:
+}+
YAML. +
request:
+ method: GET
+ url: /api/v1/xxxx
+ queryParameters:
+ foo:
+ - bar
+ - bar2
+ headers:
+ Authorization:
+ - secret
+ - secret2
+ body:
+ foo: bar
+ baz: 5
+response:
+ status: 200
+ headers:
+ Authorization: "foo {{{ request.headers.Authorization.0 }}} bar"
+ body:
+ url: "{{{ request.url }}}"
+ path: "{{{ request.path }}}"
+ pathIndex: "{{{ request.path.1 }}}"
+ param: "{{{ request.query.foo }}}"
+ paramIndex: "{{{ request.query.foo.1 }}}"
+ authorization: "{{{ request.headers.Authorization.0 }}}"
+ authorization2: "{{{ request.headers.Authorization.1 }}"
+ fullBody: "{{{ request.body }}}"
+ responseFoo: "{{{ jsonpath this '$.foo' }}}"
+ responseBaz: "{{{ jsonpath this '$.baz' }}}"
+ responseBaz2: "Bla bla {{{ jsonpath this '$.foo' }}} bla bla"+
Running a JUnit test generation leads to a test that resembles the following example:
// given: MockMvcRequestSpecification request = given() .header("Authorization", "secret") .header("Authorization", "secret2") @@ -723,12 +997,12 @@ org.springframework.cloud.contract.stubrunner.provider.wiremock.TestWireMockExte } }
![]() | Important |
|---|---|
Remember to override the |
If you work with Pact, the following discussion may seem familiar. +want the transformation to be applied only for a mapping that explicitly requires it.
If you work with Pact, the following discussion may seem familiar. Quite a few users are used to having a separation between the body and setting the dynamic parts of a contract.
You can use two separate sections:
stubMatchers, which lets you define the dynamic values that should end up in a stub.
You can set it in the request or inputMessage part of your contract.testMatchers, which is present in the response or outputMessage side of the
contract.Currently, Spring Cloud Contract Verifier supports only JSON Path-based matchers with the -following matching possibilities:
For stubMatchers:
byEquality(): The value taken from the response via the provided JSON Path must be
+following matching possibilities:Groovy DSL
For stubMatchers:
byEquality(): The value taken from the response via the provided JSON Path must be
equal to the value provided in the contract.byRegex(…): The value taken from the response via the provided JSON Path must
match the regex.byDate(): The value taken from the response via the provided JSON Path must
match the regex for an ISO Date value.byTimestamp(): The value taken from the response via the provided JSON Path must
@@ -746,7 +1020,15 @@ unflattened collection, use a custom method with the byCom
passed as an input to the custom method that you provide. For example,
byCommand('foo($it)') results in calling a foo method to which the value matching the
JSON Path gets passed. The type of the object read from the JSON can be one of the
-following, depending on the JSON path:String: If you point to a String value.JSONArray: If you point to a List.Map: If you point to a Map.Number: If you point to Integer, Double, or other kind of number.Boolean: If you point to a Boolean.
Consider the following example:
Contract contractDsl = Contract.make {
+following, depending on the JSON path:String: If you point to a String value.JSONArray: If you point to a List.Map: If you point to a Map.Number: If you point to Integer, Double, or other kind of number.Boolean: If you point to a Boolean.
YAML. Please read the Groovy section for detailed explanation of +what the types mean
For YAML the structure of a matcher looks like this
- path: $.foo + type: by_regex + value: bar
Or if you want to use one of the predefined regular expressions
+[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
Below you can find the allowed list of `type`s.
For stubMatchers:
by_equalityby_regexby_dateby_timestampby_timeFor testMatchers:
by_equalityby_regexby_dateby_timestampby_timeby_type
minOccurrence and maxOccurrence.by_commandConsider the following example:
Groovy DSL. +
Contract contractDsl = Contract.make {
request {
method 'GET'
urlPath '/get'
@@ -852,7 +1134,133 @@ following, depending on the JSON path: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'
+ matchers:
+ 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
+ headers:
+ Content-Type: application/json
+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
+ valueWithMinEmpty: []
+ valueWithMaxEmpty: []
+ key:
+ 'complex.key' : 'foo'
+ matchers:
+ 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: $.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)
+ headers:
+ Content-Type: application/json
+
In the preceding example, you can see the dynamic portions of the contract in the
matchers sections. For the request part, you can see that, for all fields but
valueWithoutAMatcher, the values of the regular expressions that the stub should
contain are explicitly set. For the valueWithoutAMatcher, the verification takes place
@@ -908,51 +1316,51 @@ statically imported to your tests. Notice that the byComma
the method name and passed the proper JSON path as a parameter to it.
The resulting WireMock stub is in the following example:
'''
{
"request" : {
- "urlPath" : "/get",
- "method" : "POST",
- "headers" : {
- "Content-Type" : {
- "matches" : "application/json.*"
- }
- },
- "bodyPatterns" : [ {
- "matchesJsonPath" : "$[?(@.['valueWithoutAMatcher'] == 'foo')]"
- }, {
- "matchesJsonPath" : "$[?(@.['valueWithTypeMatch'] == 'string')]"
- }, {
- "matchesJsonPath" : "$.['list'].['some'].['nested'][?(@.['anothervalue'] == 4)]"
- }, {
- "matchesJsonPath" : "$.['list'].['someother'].['nested'][?(@.['anothervalue'] == 4)]"
- }, {
- "matchesJsonPath" : "$.['list'].['someother'].['nested'][?(@.['json'] == 'with value')]"
- }, {
- "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 =~ /(.*)/)]"
- } ]
+ "urlPath" : "/get",
+ "method" : "POST",
+ "headers" : {
+ "Content-Type" : {
+ "matches" : "application/json.*"
+ }
+ },
+ "bodyPatterns" : [ {
+ "matchesJsonPath" : "$[?(@.['valueWithoutAMatcher'] == 'foo')]"
+ }, {
+ "matchesJsonPath" : "$[?(@.['valueWithTypeMatch'] == 'string')]"
+ }, {
+ "matchesJsonPath" : "$.['list'].['some'].['nested'][?(@.['anothervalue'] == 4)]"
+ }, {
+ "matchesJsonPath" : "$.['list'].['someother'].['nested'][?(@.['anothervalue'] == 4)]"
+ }, {
+ "matchesJsonPath" : "$.['list'].['someother'].['nested'][?(@.['json'] == 'with value')]"
+ }, {
+ "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 =~ /(.*)/)]"
+ } ]
},
"response" : {
- "status" : 200,
- "body" : "{\\"date\\":\\"2017-01-01\\",\\"dateTime\\":\\"2017-01-01T01:23:45\\",\\"number\\":123,\\"aBoolean\\":true,\\"duck\\":123,\\"alpha\\":\\"abc\\",\\"valueWithMin\\":[1,2,3],\\"time\\":\\"01:02:34\\",\\"valueWithTypeMatch\\":\\"string\\",\\"valueWithMax\\":[1,2,3],\\"valueWithMinMax\\":[1,2,3],\\"valueWithoutAMatcher\\":\\"foo\\"}",
- "headers" : {
- "Content-Type" : "application/json"
- }
+ "status" : 200,
+ "body" : "{\\"date\\":\\"2017-01-01\\",\\"dateTime\\":\\"2017-01-01T01:23:45\\",\\"number\\":123,\\"aBoolean\\":true,\\"duck\\":123,\\"alpha\\":\\"abc\\",\\"valueWithMin\\":[1,2,3],\\"time\\":\\"01:02:34\\",\\"valueWithTypeMatch\\":\\"string\\",\\"valueWithMax\\":[1,2,3],\\"valueWithMinMax\\":[1,2,3],\\"valueWithoutAMatcher\\":\\"foo\\"}",
+ "headers" : {
+ "Content-Type" : "application/json"
+ }
}
}
'''
![[Important]](images/important.png)
Important If you use a matcher, then the part of the request aned response that the
@@ -1021,7 +1429,8 @@ content type set. Otherwise, the default of application/oc
assertThatJson(parsedJson).field("['property1']").isEqualTo("a");
'''If you’re using asynchronous communication on the server side (your controllers are
returning Callable, DeferredResult, and so on), then, inside your contract, you must
-provide a sync() method in the response section. The following code shows an example:
org.springframework.cloud.contract.spec.Contract.make {
+provide a sync() method in the response section. The following code shows an example:Groovy DSL.
+
org.springframework.cloud.contract.spec.Contract.make {
request {
method GET()
url '/get'
@@ -1031,7 +1440,11 @@ provide a sync() method in the 'Passed'
async()
}
-}Spring Cloud Contract supports context paths.
![[Important]](images/important.png)
Important The only change needed to fully support context paths is the switch on the
+}
+
YAML.
+
response:
+ async: true
+
Spring Cloud Contract supports context paths.
![[Important]](images/important.png)
Important The only change needed to fully support context paths is the switch on the
PRODUCER side. Also, the autogenerated tests must use EXPLICIT mode. The consumer
side remains untouched. In order for the generated test to pass, you must use EXPLICIT
mode.
Maven.
@@ -1077,7 +1490,8 @@ socket.
Consider the following contract:
or
context path included (for example, /my-context-path/url).
Your contracts reflect that you have a context path. Your generated stubs also have
that information (for example, in the stubs, you have to call /my-context-path/url). The DSL for messaging looks a little bit different than the one that focuses on HTTP. The
following sections explain the differences:
The output message can be triggered by calling a method (such as a Scheduler when a was
-started and a message was sent), as shown in the following example:
def dsl = Contract.make {
+started and a message was sent), as shown in the following example:Groovy DSL.
+
def dsl = Contract.make {
// Human readable description
description 'Some description'
// Label by means of which the output message can be triggered
@@ -1098,11 +1512,31 @@ started and a message was sent), as shown in the following example:'BOOK-NAME', 'foo')
}
}
-}In the previous example case, the output message is sent to output if a method called
+}
+
YAML.
+
# Human readable description
+description: Some description
+# Label by means of which the output message can be triggered
+label: some_label
+input:
+ # the contract will be triggered by a method
+ triggeredBy: bookReturnedTriggered()
+# output message of the contract
+outputMessage:
+ # destination to which the output message will be sent
+ sentTo: output
+ # the body of the output message
+ body:
+ bookName: foo
+ # the headers of the output message
+ headers:
+ BOOK-NAME: foo
+
In the previous example case, the output message is sent to output if a method called
bookReturnedTriggered is executed. On the message publisher’s side, we generate a
test that calls that method to trigger the message. On the consumer side, you can use
the some_label to trigger the message.
The output message can be triggered by receiving a message, as shown in the following
-example:
def dsl = Contract.make {
+example:Groovy DSL.
+
def dsl = Contract.make {
description 'Some Description'
label 'some_label'
// input is a message
@@ -1127,11 +1561,36 @@ example:def dsl = Contract.make {
header('BOOK-NAME', 'foo')
}
}
-}In the preceding example, the output message is sent to output if a proper message is
+}
+
YAML.
+
# Human readable description
+description: Some description
+# Label by means of which the output message can be triggered
+label: some_label
+# input is a message
+input:
+ messageFrom: input
+ # has the following body
+ messageBody:
+ bookName: 'foo'
+ # and the following headers
+ messageHeaders:
+ sample: 'header'
+# output message of the contract
+outputMessage:
+ # destination to which the output message will be sent
+ sentTo: output
+ # the body of the output message
+ body:
+ bookName: foo
+ # the headers of the output message
+ headers:
+ BOOK-NAME: foo
+
In the preceding example, the output message is sent to output if a proper message is
received on the input destination. On the message publisher’s side, the engine
generates a test that sends the input message to the defined destination. On the
consumer side, you can either send a message to the input destination or use a label
-(some_label in the example) to trigger the message.
In HTTP, you have a notion of client/stub and `server/test notation. You can also
+(some_label in the example) to trigger the message.
![[Important]](images/important.png)
Important This section is valid only for Groovy DSL.
In HTTP, you have a notion of client/stub and `server/test notation. You can also
use those paradigms in messaging. In addition, Spring Cloud Contract Verifier also
provides the consumer and producer methods, as presented in the following example
(note that you can use either $ or value methods to provide consumer and producer
@@ -1152,11 +1611,12 @@ parts):
Contract.make {
bookName: 'foo'
])
}
-}You can define multiple contracts in one file. Such a contract might resemble the
-following example:
import org.springframework.cloud.contract.spec.Contract
+base class or in a static import. Spring Cloud Contract will execute that method
+in the generated test.You can define multiple contracts in one file. Such a contract might resemble the
+following example:
Groovy DSL.
+
import org.springframework.cloud.contract.spec.Contract
[
Contract.make {
@@ -1178,7 +1638,23 @@ following example:200
}
}
-]In the preceding example, one contract has the name field and the other does not. This
+]
+
YAML.
+
---
+name: should post a user
+request:
+ method: POST
+ url: /users/1
+response:
+ status: 200
+
+---
+request:
+ method: POST
+ url: /users/2
+response:
+ status: 200
+
In the preceding example, one contract has the name field and the other does not. This
leads to generation of two tests that look more or less like this:
package org.springframework.cloud.contract.verifier.tests.com.hello;
import com.example.TestBase;
diff --git a/multi/multi__customization.html b/multi/multi__customization.html
index f193132145..3522c8d4ec 100644
--- a/multi/multi__customization.html
+++ b/multi/multi__customization.html
@@ -1,6 +1,6 @@
- 9. Customization You can customize the Spring Cloud Contract Verifier by extending the DSL, as shown in
+
9. Customization ![[Important]](images/important.png)
Important This section is valid only for Groovy DSL
You can customize the Spring Cloud Contract Verifier by extending the DSL, as shown in
the remainder of this section.
You can provide your own functions to the DSL. The key requirement for this feature is to
maintain the static compatibility. Later in this document, you can see examples of:
- Creating a JAR with reusable classes.
- Referencing of these classes in the DSLs.
You can find the full example
here.
The following examples show three classes that can be reused in the DSLs.
PatternUtils contains functions used by both the consumer and the producer.
package com.example;
diff --git a/multi/multi__spring_cloud_contract_faq.html b/multi/multi__spring_cloud_contract_faq.html
index 1aedba9dac..ca450b463f 100644
--- a/multi/multi__spring_cloud_contract_faq.html
+++ b/multi/multi__spring_cloud_contract_faq.html
@@ -2,7 +2,7 @@
3. Spring Cloud Contract FAQ For the time being Spring Cloud Contract Verifier is a JVM based tool. So it could be your first pick when you’re already creating
software for the JVM. This project has a lot of really interesting features but especially quite a few of them definitely make
-Spring Cloud Contract Verifier stand out on the "market" of Consumer Driven Contract (CDC) tooling. Out of many the most interesting are:
- Possibility to do CDC with messaging
- Clear and easy to use, statically typed DSL
- Possibility to copy paste your current JSON file to the contract and only edit its elements
- Automatic generation of tests from the defined Contract
- Stub Runner functionality - the stubs are automatically downloaded at runtime from Nexus / Artifactory
- Spring Cloud integration - no discovery service is needed for integration tests
One of the biggest challenges related to stubs is their reusability. Only if they can be vastly used, will they serve their purpose.
+Spring Cloud Contract Verifier stand out on the "market" of Consumer Driven Contract (CDC) tooling. Out of many the most interesting are:
- Possibility to do CDC with messaging
- Clear and easy to use, statically typed DSL
- Possibility to copy paste your current JSON file to the contract and only edit its elements
- Automatic generation of tests from the defined Contract
- Stub Runner functionality - the stubs are automatically downloaded at runtime from Nexus / Artifactory
- Spring Cloud integration - no discovery service is needed for integration tests
One of the biggest challenges related to stubs is their reusability. Only if they can be vastly used, will they serve their purpose.
What typically makes that difficult are the hard-coded values of request / response elements. For example dates or ids.
Imagine the following JSON request
{
"time" : "2016-10-10 20:10:15",
@@ -68,19 +68,19 @@ for time and UUID are simplified and most likely invalid but we want to keep thi
])
}
}
![[Important]](images/important.png)
Important Please read the Groovy docs related to JSON to understand how to
-properly structure the request / response bodies.
Let’s try to answer a question what versioning really means. If you’re referring to the API version then there are
different approaches.
- use Hypermedia, links and do not version your API by any means
- pass versions through headers / urls
I will not try to answer a question which approach is better. Whatever suit your needs and allows you to generate
business value should be picked.
Let’s assume that you do version your API. In that case you should provide as many contracts as many versions you support.
-You can create a subfolder for every version or append it to th contract name - whatever suits you more.
If by versioning you mean the version of the JAR that contains the stubs then there are essentially two main approaches.
Let’s assume that you’re doing Continuous Delivery / Deployment which means that you’re generating a new version of
+You can create a subfolder for every version or append it to th contract name - whatever suits you more.
If by versioning you mean the version of the JAR that contains the stubs then there are essentially two main approaches.
Let’s assume that you’re doing Continuous Delivery / Deployment which means that you’re generating a new version of
the jar each time you go through the pipeline and that jar can go to production at any time. For example your jar version
looks like this (it got built on the 20.10.2016 at 20:15:21) :
1.0.0.20161020-201521-RELEASE
In that case your generated stub jar will look like this.
1.0.0.20161020-201521-RELEASE-stubs.jar
In this case you should inside your application.yml or @AutoConfigureStubRunner when referencing stubs provide the
latest version of the stubs. You can do that by passing the + sign. Example
@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:8080"})If the versioning however is fixed (e.g. 1.0.4.RELEASE or 2.1.1) then you have to set the concrete value of the jar
-version. Example for 2.1.1.
@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:2.1.1:stubs:8080"})You can manipulate the classifier to run the tests against current development version of the stubs of other services
+version. Example for 2.1.1.
@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:2.1.1:stubs:8080"})You can manipulate the classifier to run the tests against current development version of the stubs of other services
or the ones that were deployed to production. If you alter your build to deploy the stubs with the prod-stubs classifier
- once you reach production deployment then you can run tests in one case with dev stubs and one with prod stubs.
Example of tests using development version of stubs
@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:8080"})Example of tests using production version of stubs
@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:prod-stubs:8080"})You can pass those values also via properties from your deployment pipeline.
Another way of storing contracts other than having them with the producer is keeping them in a common place.
+ once you reach production deployment then you can run tests in one case with dev stubs and one with prod stubs.
Example of tests using development version of stubs
@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:8080"})Example of tests using production version of stubs
@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:prod-stubs:8080"})You can pass those values also via properties from your deployment pipeline.
Another way of storing contracts other than having them with the producer is keeping them in a common place.
It can be related to security issues where the consumers can’t clone the producer’s code. Also if you keep
contracts in a single place then you, as a producer, will know how many consumers you have and which
-consumer will you break with your local changes.
Let’s assume that we have a producer with coordinates com.example:server and 3 consumers: client1,
+consumer will you break with your local changes.
Let’s assume that we have a producer with coordinates com.example:server and 3 consumers: client1,
client2, client3. Then in the repository with common contracts you would have the following setup
(which you can checkout here:
├── com
│ └── example
@@ -115,7 +115,7 @@ one to one to the contents of the repo.Example of a <parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
- <version>2.0.0.M6</version>
+ <version>2.0.0.BUILD-SNAPSHOT</version>
<relativePath />
</parent>
@@ -271,11 +271,11 @@ Those poms are necessary for the consumer side to run mvn
</excludes>
</fileSet>
</fileSets>
-</assembly>
The workflow would look similar to the one presented in the Step by step guide to CDC. The only difference
+</assembly>
The workflow would look similar to the one presented in the Step by step guide to CDC. The only difference
is that the producer doesn’t own the contracts anymore. So the consumer and the producer have to work on
- common contracts in a common repository.
When the consumer wants to work on the contracts offline, instead of cloning the producer code, the
+ common contracts in a common repository.
When the consumer wants to work on the contracts offline, instead of cloning the producer code, the
consumer team clones the common repository, goes to the required producer’s folder (e.g. com/example/server)
-and runs mvn clean install -DskipTests to install locally the stubs converted from the contracts.
![[Tip]](images/tip.png)
Tip You need to have Maven installed locally
As a producer it’s enough to alter the Spring Cloud Contract Verifier to provide the URL and the dependency
+and runs mvn clean install -DskipTests to install locally the stubs converted from the contracts.
![[Tip]](images/tip.png)
Tip You need to have Maven installed locally
As a producer it’s enough to alter the Spring Cloud Contract Verifier to provide the URL and the dependency
of the JAR containing the contracts:
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
@@ -291,12 +291,13 @@ of the JAR containing the contracts:http://link/to/your/nexus/or/artifactory/or/sth. It will be then unpacked in a local temporary folder
and contracts present under the com/example/server will be picked as the ones used to generate the
tests and the stubs. Due to this convention the producer team will know which consumer teams will be broken
-when some incompatible changes are done.The rest of the flow looks the same.
Yes! Check out the Different base classes for contracts sections
-of either Gradle or Maven plugins.
The generated tests all boil down to RestAssured in some form or fashion which relies on Apache HttpClient. HttpClient has a facility called wire logging which logs the entire request and response to HttpClient. Spring Boot has a logging common application property for doing this sort of thing, just add this to your application properties
logging.level.org.apache.http.wire=DEBUGYes! Check out the Different base classes for contracts sections
+of either Gradle or Maven plugins.
The generated tests all boil down to RestAssured in some form or fashion which relies on Apache HttpClient. HttpClient has a facility called wire logging which logs the entire request and response to HttpClient. Spring Boot has a logging common application property for doing this sort of thing, just add this to your application properties
logging.level.org.apache.http.wire=DEBUGStarting from version 1.2.0 we turn on WireMock logging to
info and the WireMock notifier to being verbose. Now you will
exactly know what request was received by WireMock server and which
-matching response definition was picked.
To turn off this feature just bump WireMock logging to ERROR
logging.level.com.github.tomakehurst.wiremock=ERRORYou can use the mappingsOutputFolder property on @AutoConfigureStubRunner or StubRunnerRule
+matching response definition was picked.
To turn off this feature just bump WireMock logging to ERROR
logging.level.com.github.tomakehurst.wiremock=ERRORYou can use the mappingsOutputFolder property on @AutoConfigureStubRunner or StubRunnerRule
to dump all mappings per artifact id. Also the port at which the given stub server was
-started will be attached.
Yes! With version 1.1.0 we’ve added such a possibility. On the HTTP stub server side we’re providing support
-for this for WireMock. In case of other HTTP server stubs you’ll have to implement the approach yourself.
\ No newline at end of file
+started will be attached.
Yes! With version 1.1.0 we’ve added such a possibility. On the HTTP stub server side we’re providing support
+for this for WireMock. In case of other HTTP server stubs you’ll have to implement the approach yourself.
\ No newline at end of file
diff --git a/multi/multi__spring_cloud_contract_stub_runner.html b/multi/multi__spring_cloud_contract_stub_runner.html
index 9077f2ba3b..9c6fca8211 100644
--- a/multi/multi__spring_cloud_contract_stub_runner.html
+++ b/multi/multi__spring_cloud_contract_stub_runner.html
@@ -89,9 +89,7 @@ it if you want to.
Maven.
<inherited>false</inherited>
<configuration>
<attach>true</attach>
- <descriptors>
- ${basedir}/src/assembly/stub.xml
- </descriptors>
+ <descriptor>${basedir}/src/assembly/stub.xml</descriptor>
</configuration>
</execution>
</executions>
@@ -688,4 +686,29 @@ pass an empty classifier this way: groupId:artifactId:vers
downloaded.
port means the port of the WireMock server.
![]() | Important |
|---|---|
Starting with version 1.0.4, you can provide a range of versions that you would like the Stub Runner to take into consideration. You can read more about the Aether versioning -ranges here. |