diff --git a/multi/multi__spring_cloud_contract_verifier_introduction.html b/multi/multi__spring_cloud_contract_verifier_introduction.html index 945b7fdded..f23350369e 100644 --- a/multi/multi__spring_cloud_contract_verifier_introduction.html +++ b/multi/multi__spring_cloud_contract_verifier_introduction.html @@ -171,9 +171,28 @@ compliance with the added contracts. By default, the generated tests are under assertThatJson(parsedJson).field("['fraudCheckStatus']").matches("[A-Z]{5}"); assertThatJson(parsedJson).field("['rejection.reason']").isEqualTo("Amount too high"); }

The preceding example uses Spring’s MockMvc to run the tests. This is the default test -mode for HTTP contracts. However, JAX-RX client and explicit HTTP invocations can also be +mode for HTTP contracts. However, JAX-RS client and explicit HTTP invocations can also be used. (To do so, change the testMode property of the plugin to JAX-RS or EXPLICIT, -respectively.)

Apart from the default JUnit 4, you can instead use JUnit 5 or Spock tests, by setting the plugin +respectively.)

Since 2.1.0, it is also possible to use RestAssuredWebTestClient`with Spring’s reactive `WebTestClient +run under the hood. This is particularly recommended while working with Reactive, Web-Flux-based applications. +In order to use WebTestClient set testMode to WEBTESTCLIENT.

Here is an example of a test generated in WEBTESTCLIENT test mode:

[source,java,indent=0]
@Test
+	public void validate_shouldRejectABeerIfTooYoung() throws Exception {
+		// given:
+			WebTestClientRequestSpecification request = given()
+					.header("Content-Type", "application/json")
+					.body("{\"age\":10}");
+
+		// when:
+			WebTestClientResponse response = given().spec(request)
+					.post("/check");
+
+		// 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("['status']").isEqualTo("NOT_OK");
+	}

Apart from the default JUnit 4, you can instead use JUnit 5 or Spock tests, by setting the plugin testFramework property to either JUNIT5 or Spock.

[Tip]Tip

You can now also generate WireMock scenarios based on the contracts, by including an order number followed by an underscore at the beginning of the contract file names.

The following example shows an auto-generated test in Spock for a messaging stub contract:

[source,groovy,indent=0]
given:
 	 ContractVerifierMessage inputMessage = contractVerifierMessaging.create(
diff --git a/multi/multi__spring_cloud_contract_verifier_setup.html b/multi/multi__spring_cloud_contract_verifier_setup.html
index ab2c443be2..8d838962e9 100644
--- a/multi/multi__spring_cloud_contract_verifier_setup.html
+++ b/multi/multi__spring_cloud_contract_verifier_setup.html
@@ -116,7 +116,7 @@ shown here:

contracts {
 	baseClassForTests = 'org.mycompany.tests'
 	generatedTestSourcesDir = project.file('src/generatedContract')
 }

4.1.9 Configuration Options

  • testMode: Defines the mode for acceptance tests. By default, the mode is MockMvc, -which is based on Spring’s MockMvc. It can also be changed to JaxRsClient or to +which is based on Spring’s MockMvc. It can also be changed to WebTestClient, JaxRsClient or to Explicit for real HTTP calls.
  • imports: Creates an array with imports that should be included in generated tests (for example ['org.myorg.Matchers']). By default, it creates an empty array.
  • staticImports: Creates an array with static imports that should be included in generated tests(for example ['org.myorg.Matchers.*']). By default, it creates an empty @@ -356,7 +356,7 @@ definition or the execution definition, as shown he <baseClassForTests>org.springframework.cloud.verifier.twitter.place.BaseMockMvcSpec</baseClassForTests> </configuration> </plugin>

4.2.7 Configuration Options

11.7 Generating Stubs using REST Docs

Spring REST Docs can be used to generate documentation (for example in Asciidoctor format) for an HTTP API with Spring MockMvc -or WebTestClient or -Rest Assured. At the same time that you generate documentation for your API, you can also +or WebTestClient or Rest Assured. At the same time that you generate documentation for your API, you can also generate WireMock stubs by using Spring Cloud Contract WireMock. To do so, write your normal REST Docs test cases and use @AutoConfigureRestDocs to have stubs be automatically generated in the REST Docs output directory. The following code shows an diff --git a/multi/multi__using_the_pluggable_architecture.html b/multi/multi__using_the_pluggable_architecture.html index 620642f441..4cd749eac3 100644 --- a/multi/multi__using_the_pluggable_architecture.html +++ b/multi/multi__using_the_pluggable_architecture.html @@ -422,10 +422,10 @@ to clone the repository and use it as a source of contracts to generate tests or stubs.

Either via environment variables, system properties, properties set inside the plugin or contracts repository configuration you can tweak the downloader’s behaviour. Below you can find the list of -properties

Table 10.1. SCM Stub Downloader properties

Type of a property

Name of the property

Description

* git.branch (plugin prop)

* stubrunner.properties.git.branch (system prop)

* STUBRUNNER_PROPERTIES_GIT_BRANCH (env prop)

master

Which branch to checkout

* git.username (plugin prop)

* stubrunner.properties.git.username (system prop)

* STUBRUNNER_PROPERTIES_GIT_USERNAME (env prop)

 

Git clone username

* git.password (plugin prop)

* stubrunner.properties.git.password (system prop)

* STUBRUNNER_PROPERTIES_GIT_PASSWORD (env prop)

 

Git clone password

* git.no-of-attempts (plugin prop)

* stubrunner.properties.git.no-of-attempts (system prop)

* STUBRUNNER_PROPERTIES_GIT_NO_OF_ATTEMPTS (env prop)

10

Number of attempts to push the commits to origin

* git.wait-between-attempts (Plugin prop)

* stubrunner.properties.git.wait-between-attempts (system prop)

* STUBRUNNER_PROPERTIES_GIT_WAIT_BETWEEN_ATTEMPTS (env prop)

1000

Number of millis to wait between attempts to push the commits to origin


10.7 Using the Pact Stub Downloader

Whenever the repositoryRoot starts with a Pact protocol +properties

Table 10.1. SCM Stub Downloader properties

Type of a property

Name of the property

Description

* git.branch (plugin prop)

* stubrunner.properties.git.branch (system prop)

* STUBRUNNER_PROPERTIES_GIT_BRANCH (env prop)

master

Which branch to checkout

* git.username (plugin prop)

* stubrunner.properties.git.username (system prop)

* STUBRUNNER_PROPERTIES_GIT_USERNAME (env prop)

 

Git clone username

* git.password (plugin prop)

* stubrunner.properties.git.password (system prop)

* STUBRUNNER_PROPERTIES_GIT_PASSWORD (env prop)

 

Git clone password

* git.no-of-attempts (plugin prop)

* stubrunner.properties.git.no-of-attempts (system prop)

* STUBRUNNER_PROPERTIES_GIT_NO_OF_ATTEMPTS (env prop)

10

Number of attempts to push the commits to origin

* git.wait-between-attempts (Plugin prop)

* stubrunner.properties.git.wait-between-attempts (system prop)

* STUBRUNNER_PROPERTIES_GIT_WAIT_BETWEEN_ATTEMPTS (env prop)

1000

Number of millis to wait between attempts to push the commits to origin


10.7 Using the Pact Stub Downloader

Whenever the repositoryRoot starts with a Pact protocol (starts with pact://), the stub downloader will try to fetch the Pact contract definitions from the Pact Broker. Whatever is set after pact:// will be parsed as the Pact Broker URL.

Either via environment variables, system properties, properties set inside the plugin or contracts repository configuration you can tweak the downloader’s behaviour. Below you can find the list of -properties

Table 10.2. SCM Stub Downloader properties

Name of a property

Default

Description

* pactbroker.host (plugin prop)

* stubrunner.properties.pactbroker.host (system prop)

* STUBRUNNER_PROPERTIES_PACTBROKER_HOST (env prop)

Host from URL passed to repositoryRoot

What is the URL of Pact Broker

* pactbroker.port (plugin prop)

* stubrunner.properties.pactbroker.port (system prop)

* STUBRUNNER_PROPERTIES_PACTBROKER_PORT (env prop)

Port from URL passed to repositoryRoot

What is the port of Pact Broker

* pactbroker.protocol (plugin prop)

* stubrunner.properties.pactbroker.protocol (system prop)

* STUBRUNNER_PROPERTIES_PACTBROKER_PROTOCOL (env prop)

Protocol from URL passed to repositoryRoot

What is the protocol of Pact Broker

* pactbroker.tags (plugin prop)

* stubrunner.properties.pactbroker.tags (system prop)

* STUBRUNNER_PROPERTIES_PACTBROKER_TAGS (env prop)

Version of the stub, or latest if version is +

What tags should be used to fetch the stub

* pactbroker.auth.scheme (plugin prop)

* stubrunner.properties.pactbroker.auth.scheme (system prop)

* STUBRUNNER_PROPERTIES_PACTBROKER_AUTH_SCHEME (env prop)

Basic

What kind of authentication should be used to connect to the Pact Broker

* pactbroker.auth.username (plugin prop)

* stubrunner.properties.pactbroker.auth.username (system prop)

* STUBRUNNER_PROPERTIES_PACTBROKER_AUTH_USERNAME (env prop)

The username passed to contractsRepositoryUsername (maven) or contractRepository.username (gradle)

Username used to connect to the Pact Broker

* pactbroker.auth.password (plugin prop)

* stubrunner.properties.pactbroker.auth.password (system prop)

* STUBRUNNER_PROPERTIES_PACTBROKER_AUTH_PASSWORD (env prop)

The password passed to contractsRepositoryPassword (maven) or contractRepository.password (gradle)

Password used to connect to the Pact Broker

* pactbroker.provider-name-with-group-id (plugin prop)

* stubrunner.properties.pactbroker.provider-name-with-group-id (system prop)

* STUBRUNNER_PROPERTIES_PACTBROKER_PROVIDER_NAME_WITH_GROUP_ID (env prop)

false

When true, the provider name will be a combination of groupId:artifactId. If false, just artifactId is used


\ No newline at end of file +properties

Table 10.2. SCM Stub Downloader properties

Name of a property

Default

Description

* pactbroker.host (plugin prop)

* stubrunner.properties.pactbroker.host (system prop)

* STUBRUNNER_PROPERTIES_PACTBROKER_HOST (env prop)

Host from URL passed to repositoryRoot

What is the URL of Pact Broker

* pactbroker.port (plugin prop)

* stubrunner.properties.pactbroker.port (system prop)

* STUBRUNNER_PROPERTIES_PACTBROKER_PORT (env prop)

Port from URL passed to repositoryRoot

What is the port of Pact Broker

* pactbroker.protocol (plugin prop)

* stubrunner.properties.pactbroker.protocol (system prop)

* STUBRUNNER_PROPERTIES_PACTBROKER_PROTOCOL (env prop)

Protocol from URL passed to repositoryRoot

What is the protocol of Pact Broker

* pactbroker.tags (plugin prop)

* stubrunner.properties.pactbroker.tags (system prop)

* STUBRUNNER_PROPERTIES_PACTBROKER_TAGS (env prop)

Version of the stub, or latest if version is +

What tags should be used to fetch the stub

* pactbroker.auth.scheme (plugin prop)

* stubrunner.properties.pactbroker.auth.scheme (system prop)

* STUBRUNNER_PROPERTIES_PACTBROKER_AUTH_SCHEME (env prop)

Basic

What kind of authentication should be used to connect to the Pact Broker

* pactbroker.auth.username (plugin prop)

* stubrunner.properties.pactbroker.auth.username (system prop)

* STUBRUNNER_PROPERTIES_PACTBROKER_AUTH_USERNAME (env prop)

The username passed to contractsRepositoryUsername (maven) or contractRepository.username (gradle)

Username used to connect to the Pact Broker

* pactbroker.auth.password (plugin prop)

* stubrunner.properties.pactbroker.auth.password (system prop)

* STUBRUNNER_PROPERTIES_PACTBROKER_AUTH_PASSWORD (env prop)

The password passed to contractsRepositoryPassword (maven) or contractRepository.password (gradle)

Password used to connect to the Pact Broker

* pactbroker.provider-name-with-group-id (plugin prop)

* stubrunner.properties.pactbroker.provider-name-with-group-id (system prop)

* STUBRUNNER_PROPERTIES_PACTBROKER_PROVIDER_NAME_WITH_GROUP_ID (env prop)

false

When true, the provider name will be a combination of groupId:artifactId. If false, just artifactId is used


\ No newline at end of file diff --git a/multi/multi_contract-dsl.html b/multi/multi_contract-dsl.html index 97bad0c2db..209a9d6886 100644 --- a/multi/multi_contract-dsl.html +++ b/multi/multi_contract-dsl.html @@ -8,48 +8,7 @@ typed, to make it programmer-readable without any knowledge of the DSL itself.Contract class and make static imports, such as org.springframework.cloud.spec.Contract.make { …​ }. You can also provide an import to the Contract class: import org.springframework.cloud.spec.Contract and then call -Contract.make { …​ }.

[Tip]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'
-		headers {
-			header 'Content-Type': 'application/vnd.org.springframework.cloud.contract.verifier.twitter-places-analyzer.v1+json'
-		}
-		body '''\
-		[{
-			"created_at": "Sat Jul 26 09:38:57 +0000 2014",
-			"id": 492967299297845248,
-			"id_str": "492967299297845248",
-			"text": "Gonna see you at Warsaw",
-			"place":
-			{
-				"attributes":{},
-				"bounding_box":
-				{
-					"coordinates":
-						[[
-							[-77.119759,38.791645],
-							[-76.909393,38.791645],
-							[-76.909393,38.995548],
-							[-77.119759,38.995548]
-						]],
-					"type":"Polygon"
-				},
-				"country":"United States",
-				"country_code":"US",
-				"full_name":"Washington, DC",
-				"id":"01fbe706f872cb32",
-				"name":"Washington",
-				"place_type":"city",
-				"url": "http://api.twitter.com/1/geo/id/01fbe706f872cb32.json"
-			}
-		}]
-	'''
-	}
-	response {
-		status OK()
-	}
-}

The following is a complete example of a YAML contract definition:

description: Some description
+Contract.make { …​ }.

[Tip]Tip

Spring Cloud Contract supports defining multiple contracts in a single file.

The following is a complete example of a Groovy contract definition:

The following is a complete example of a YAML contract definition:

description: Some description
 name: some name
 priority: 8
 ignored: true
@@ -454,50 +413,7 @@ 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"
-		headers {
-			contentType('multipart/form-data;boundary=AaB03x')
-		}
-		multipart(
-				// key (parameter name), value (parameter value) pair
-				formParameter: $(c(regex('".+"')), p('"formParameterValue"')),
-				someBooleanParameter: $(c(regex(anyBoolean())), p('true')),
-				// a named parameter (e.g. with `file` name) that represents file with
-				// `name` and `content`. You can also call `named("fileName", "fileContent")`
-				file: named(
-						// name of the file
-						name: $(c(regex(nonEmpty())), p('filename.csv')),
-						// content of the file
-						content: $(c(regex(nonEmpty())), p('file content')),
-						// content type for the part
-						contentType: $(c(regex(nonEmpty())), p('application/json')))
-		)
-	}
-	response {
-		status OK()
-	}
-}
-org.springframework.cloud.contract.spec.Contract contractDsl = org.springframework.cloud.contract.spec.Contract.make {
-	request {
-		method "PUT"
-		url "/multipart"
-		headers {
-			contentType('multipart/form-data;boundary=AaB03x')
-		}
-		multipart(
-				file: named(
-						name: value(stub(regex('.+')), test('file')),
-						content: value(stub(regex('.+')), test([100, 117, 100, 97] as byte[]))
-				)
-		)
-	}
-	response {
-		status 200
-	}
-}

+

YAML. 

request:
   method: PUT
@@ -637,27 +553,7 @@ need to use patterns and not exact values both for your test and your server sid
 	}
 }

You can also provide only one side of the communication with a regular expression. If you do so, then the contract engine automatically provides the generated string that matches -the provided regular expression. The following code shows an example:

org.springframework.cloud.contract.spec.Contract.make {
-	request {
-		method 'PUT'
-		url value(consumer(regex('/foo/[0-9]{5}')))
-		body([
-			requestElement: $(consumer(regex('[0-9]{5}')))
-		])
-		headers {
-			header('header', $(consumer(regex('application\\/vnd\\.fraud\\.v1\\+json;.*'))))
-		}
-	}
-	response {
-		status OK()
-		body([
-			responseElement: $(producer(regex('[0-9]{7}')))
-		])
-		headers {
-			contentType("application/vnd.fraud.v1+json")
-		}
-	}
-}

In the preceding example, the opposite side of the communication has the respective data +the provided regular expression. The following code shows an example:

In the preceding example, the opposite side of the communication has the respective data generated for request and response.

Spring Cloud Contract comes with a series of predefined regular expressions that you can use in your contracts, as shown in the following example:

protected static final Pattern TRUE_OR_FALSE = Pattern.compile(/(true|false)/)
 protected static final Pattern ALPHA_NUMERIC = Pattern.compile('[a-zA-Z0-9]+')
@@ -758,30 +654,7 @@ Pattern nonEmpty() {
 
 Pattern nonBlank() {
 	return NON_BLANK
-}

In your contract, you can use it as shown in the following example:

Contract dslWithOptionalsInString = Contract.make {
-	priority 1
-	request {
-		method POST()
-		url '/users/password'
-		headers {
-			contentType(applicationJson())
-		}
-		body(
-				email: $(consumer(optional(regex(email()))), producer('abc@abc.com')),
-				callback_url: $(consumer(regex(hostname())), producer('http://partners.com'))
-		)
-	}
-	response {
-		status 404
-		headers {
-			contentType(applicationJson())
-		}
-		body(
-				code: value(consumer("123123"), producer(optional("123123"))),
-				message: "User not found by email = [${value(producer(regex(email())), consumer('not.existing@user.com'))}]"
-		)
-	}
-}

8.5.3 Passing Optional Parameters

[Important]Important

This section is valid only for Groovy DSL. Check out the +}

In your contract, you can use it as shown in the following example:

8.5.3 Passing Optional Parameters

[Important]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:

  • STUB side of the Request
  • TEST side of the Response

The following example shows how to provide optional parameters:

org.springframework.cloud.contract.spec.Contract.make {
 	priority 1
@@ -891,16 +764,16 @@ ensure that the authToken() method returns everythi
 JSON path:

  • String: If you point to a String value in the JSON.
  • JSONArray: If you point to a List in the JSON.
  • Map: If you point to a Map in the JSON.
  • Number: If you point to Integer, Double etc. in the JSON.
  • Boolean: If you point to a Boolean in the JSON.

In the request part of the contract, you can specify that the body should be taken from a method.

[Important]Important

You must provide both the consumer and the producer side. The execute part is applied for the whole body - not for parts of it.

The following example shows how to read an object from JSON:

Contract contractDsl = Contract.make {
-	request {
-		method 'GET'
-		url '/something'
-		body(
-				$(c("foo"), p(execute("hashCode()")))
-		)
-	}
-	response {
-		status OK()
-	}
+    request {
+        method 'GET'
+        url '/something'
+        body(
+                $(c('foo'), p(execute('hashCode()')))
+        )
+    }
+    response {
+        status OK()
+    }
 }

The preceding example results in calling the hashCode() method in the request body. It should resemble the following code:

// given:
  MockMvcRequestSpecification request = given()
@@ -922,80 +795,7 @@ matches the JSON Path.

If you’re using the YAML contract 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') {
    -			queryParameters {
    -				parameter("foo", "bar")
    -				parameter("foo", "bar2")
    -			}
    -		}
    -		headers {
    -			header(authorization(), "secret")
    -			header(authorization(), "secret2")
    -		}
    -		body(foo: "bar", baz: 5)
    -	}
    -	response {
    -		status OK()
    -		headers {
    -			header(authorization(), "foo ${fromRequest().header(authorization())} bar")
    -		}
    -		body(
    -				url: fromRequest().url(),
    -				path: fromRequest().path(),
    -				pathIndex: fromRequest().path(1),
    -				param: fromRequest().query("foo"),
    -				paramIndex: fromRequest().query("foo", 1),
    -				authorization: fromRequest().header("Authorization"),
    -				authorization2: fromRequest().header("Authorization", 1),
    -				fullBody: fromRequest().body(),
    -				responseFoo: fromRequest().body('$.foo'),
    -				responseBaz: fromRequest().body('$.baz'),
    -				responseBaz2: "Bla bla ${fromRequest().body('$.foo')} bla bla",
    -				rawUrl: fromRequest().rawUrl(),
    -				rawPath: fromRequest().rawPath(),
    -				rawPathIndex: fromRequest().rawPath(1),
    -				rawParam: fromRequest().rawQuery("foo"),
    -				rawParamIndex: fromRequest().rawQuery("foo", 1),
    -				rawAuthorization: fromRequest().rawHeader("Authorization"),
    -				rawAuthorization2: fromRequest().rawHeader("Authorization", 1),
    -				rawResponseFoo: fromRequest().rawBody('$.foo'),
    -				rawResponseBaz: fromRequest().rawBody('$.baz'),
    -				rawResponseBaz2: "Bla bla ${fromRequest().rawBody('$.foo')} bla bla"
    -		)
    -	}
    -}
    -Contract contractDsl = Contract.make {
    -	request {
    -		method 'GET'
    -		url('/api/v1/xxxx') {
    -			queryParameters {
    -				parameter("foo", "bar")
    -				parameter("foo", "bar2")
    -			}
    -		}
    -		headers {
    -			header(authorization(), "secret")
    -			header(authorization(), "secret2")
    -		}
    -		body(foo: "bar", baz: 5)
    -	}
    -	response {
    -		status OK()
    -		headers {
    -			contentType(applicationJson())
    -		}
    -		body('''
    -				{
    -					"responseFoo": "{{{ jsonPath request.body '$.foo' }}}",
    -					"responseBaz": {{{ jsonPath request.body '$.baz' }}},
    -					"responseBaz2": "Bla bla {{{ jsonPath request.body '$.foo' }}} bla bla"
    -				}
    -		'''.toString())
    -	}
    -}

    +

    YAML. 

    request:
       method: GET
    @@ -1165,122 +965,122 @@ email, url, uuid, iso_date, iso_date_time, iso_time, iso_8601_with_offset, non_e
       type: by_regex
       predefined: only_alpha_unicode

    Below you can find the allowed list of `type`s.

    • For stubMatchers:

      • by_equality
      • by_regex
      • by_date
      • by_timestamp
      • by_time
    • For testMatchers:

      • by_equality
      • by_regex
      • by_date
      • by_timestamp
      • by_time
      • by_type

        • there are 2 additional fields accepted: minOccurrence and maxOccurrence.
      • by_command
      • by_null

    Consider the following example:

    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}"))
    -			jsonPath('$.duck', byEquality())
    -			jsonPath('$.alpha', byRegex(onlyAlphaUnicode()))
    -			jsonPath('$.alpha', byEquality())
    -			jsonPath('$.number', byRegex(number()))
    -			jsonPath('$.aBoolean', byRegex(anyBoolean()))
    -			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}"))
    -			// asserts the jsonpath value against the provided value
    -			jsonPath('$.duck', byEquality())
    -			// asserts the jsonpath value against some default regex
    -			jsonPath('$.alpha', byRegex(onlyAlphaUnicode()))
    -			jsonPath('$.alpha', byEquality())
    -			jsonPath('$.number', byRegex(number()))
    -			jsonPath('$.positiveInteger', byRegex(anInteger()))
    -			jsonPath('$.negativeInteger', byRegex(anInteger()))
    -			jsonPath('$.positiveDecimalNumber', byRegex(aDouble()))
    -			jsonPath('$.negativeDecimalNumber', byRegex(aDouble()))
    -			jsonPath('$.aBoolean', byRegex(anyBoolean()))
    -			// 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}'))))
    -		}
    -	}
    +    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}"))
    +            jsonPath('$.duck', byEquality())
    +            jsonPath('$.alpha', byRegex(onlyAlphaUnicode()))
    +            jsonPath('$.alpha', byEquality())
    +            jsonPath('$.number', byRegex(number()))
    +            jsonPath('$.aBoolean', byRegex(anyBoolean()))
    +            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}"))
    +            // asserts the jsonpath value against the provided value
    +            jsonPath('$.duck', byEquality())
    +            // asserts the jsonpath value against some default regex
    +            jsonPath('$.alpha', byRegex(onlyAlphaUnicode()))
    +            jsonPath('$.alpha', byEquality())
    +            jsonPath('$.number', byRegex(number()))
    +            jsonPath('$.positiveInteger', byRegex(anInteger()))
    +            jsonPath('$.negativeInteger', byRegex(anInteger()))
    +            jsonPath('$.positiveDecimalNumber', byRegex(aDouble()))
    +            jsonPath('$.negativeDecimalNumber', byRegex(aDouble()))
    +            jsonPath('$.aBoolean', byRegex(anyBoolean()))
    +            // 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:
    diff --git a/single/spring-cloud-contract.html b/single/spring-cloud-contract.html
    index 0564fd3032..98a0cb906d 100644
    --- a/single/spring-cloud-contract.html
    +++ b/single/spring-cloud-contract.html
    @@ -176,9 +176,28 @@ compliance with the added contracts. By default, the generated tests are under
             assertThatJson(parsedJson).field("['fraudCheckStatus']").matches("[A-Z]{5}");
             assertThatJson(parsedJson).field("['rejection.reason']").isEqualTo("Amount too high");
     }

    The preceding example uses Spring’s MockMvc to run the tests. This is the default test -mode for HTTP contracts. However, JAX-RX client and explicit HTTP invocations can also be +mode for HTTP contracts. However, JAX-RS client and explicit HTTP invocations can also be used. (To do so, change the testMode property of the plugin to JAX-RS or EXPLICIT, -respectively.)

    Apart from the default JUnit 4, you can instead use JUnit 5 or Spock tests, by setting the plugin +respectively.)

    Since 2.1.0, it is also possible to use RestAssuredWebTestClient`with Spring’s reactive `WebTestClient +run under the hood. This is particularly recommended while working with Reactive, Web-Flux-based applications. +In order to use WebTestClient set testMode to WEBTESTCLIENT.

    Here is an example of a test generated in WEBTESTCLIENT test mode:

    [source,java,indent=0]
    @Test
    +	public void validate_shouldRejectABeerIfTooYoung() throws Exception {
    +		// given:
    +			WebTestClientRequestSpecification request = given()
    +					.header("Content-Type", "application/json")
    +					.body("{\"age\":10}");
    +
    +		// when:
    +			WebTestClientResponse response = given().spec(request)
    +					.post("/check");
    +
    +		// 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("['status']").isEqualTo("NOT_OK");
    +	}

    Apart from the default JUnit 4, you can instead use JUnit 5 or Spock tests, by setting the plugin testFramework property to either JUNIT5 or Spock.

    [Tip]Tip

    You can now also generate WireMock scenarios based on the contracts, by including an order number followed by an underscore at the beginning of the contract file names.

    The following example shows an auto-generated test in Spock for a messaging stub contract:

    [source,groovy,indent=0]
    given:
     	 ContractVerifierMessage inputMessage = contractVerifierMessaging.create(
    @@ -1499,7 +1518,7 @@ shown here:

    contracts {
     	baseClassForTests = 'org.mycompany.tests'
     	generatedTestSourcesDir = project.file('src/generatedContract')
     }

    4.1.9 Configuration Options

    • testMode: Defines the mode for acceptance tests. By default, the mode is MockMvc, -which is based on Spring’s MockMvc. It can also be changed to JaxRsClient or to +which is based on Spring’s MockMvc. It can also be changed to WebTestClient, JaxRsClient or to Explicit for real HTTP calls.
    • imports: Creates an array with imports that should be included in generated tests (for example ['org.myorg.Matchers']). By default, it creates an empty array.
    • staticImports: Creates an array with static imports that should be included in generated tests(for example ['org.myorg.Matchers.*']). By default, it creates an empty @@ -1739,7 +1758,7 @@ definition or the execution definition, as shown he <baseClassForTests>org.springframework.cloud.verifier.twitter.place.BaseMockMvcSpec</baseClassForTests> </configuration> </plugin>

    4.2.7 Configuration Options

    • testMode: Defines the mode for acceptance tests. By default, the mode is MockMvc, -which is based on Spring’s MockMvc. It can also be changed to JaxRsClient or to +which is based on Spring’s MockMvc. It can also be changed to WebTestClient, JaxRsClient or to Explicit for real HTTP calls.
    • basePackageForTests: Specifies the base package for all generated tests. If not set, the value is picked from baseClassForTests’s package and from `packageWithBaseClasses. If neither of these values are set, then the value is set to @@ -3380,48 +3399,7 @@ typed, to make it programmer-readable without any knowledge of the DSL itself.Contract class and make static imports, such as org.springframework.cloud.spec.Contract.make { …​ }. You can also provide an import to the Contract class: import org.springframework.cloud.spec.Contract and then call -Contract.make { …​ }.

    [Tip]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'
    -		headers {
    -			header 'Content-Type': 'application/vnd.org.springframework.cloud.contract.verifier.twitter-places-analyzer.v1+json'
    -		}
    -		body '''\
    -		[{
    -			"created_at": "Sat Jul 26 09:38:57 +0000 2014",
    -			"id": 492967299297845248,
    -			"id_str": "492967299297845248",
    -			"text": "Gonna see you at Warsaw",
    -			"place":
    -			{
    -				"attributes":{},
    -				"bounding_box":
    -				{
    -					"coordinates":
    -						[[
    -							[-77.119759,38.791645],
    -							[-76.909393,38.791645],
    -							[-76.909393,38.995548],
    -							[-77.119759,38.995548]
    -						]],
    -					"type":"Polygon"
    -				},
    -				"country":"United States",
    -				"country_code":"US",
    -				"full_name":"Washington, DC",
    -				"id":"01fbe706f872cb32",
    -				"name":"Washington",
    -				"place_type":"city",
    -				"url": "http://api.twitter.com/1/geo/id/01fbe706f872cb32.json"
    -			}
    -		}]
    -	'''
    -	}
    -	response {
    -		status OK()
    -	}
    -}

    The following is a complete example of a YAML contract definition:

    description: Some description
    +Contract.make { …​ }.

    [Tip]Tip

    Spring Cloud Contract supports defining multiple contracts in a single file.

    The following is a complete example of a Groovy contract definition:

    The following is a complete example of a YAML contract definition:

    description: Some description
     name: some name
     priority: 8
     ignored: true
    @@ -3826,50 +3804,7 @@ 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"
    -		headers {
    -			contentType('multipart/form-data;boundary=AaB03x')
    -		}
    -		multipart(
    -				// key (parameter name), value (parameter value) pair
    -				formParameter: $(c(regex('".+"')), p('"formParameterValue"')),
    -				someBooleanParameter: $(c(regex(anyBoolean())), p('true')),
    -				// a named parameter (e.g. with `file` name) that represents file with
    -				// `name` and `content`. You can also call `named("fileName", "fileContent")`
    -				file: named(
    -						// name of the file
    -						name: $(c(regex(nonEmpty())), p('filename.csv')),
    -						// content of the file
    -						content: $(c(regex(nonEmpty())), p('file content')),
    -						// content type for the part
    -						contentType: $(c(regex(nonEmpty())), p('application/json')))
    -		)
    -	}
    -	response {
    -		status OK()
    -	}
    -}
    -org.springframework.cloud.contract.spec.Contract contractDsl = org.springframework.cloud.contract.spec.Contract.make {
    -	request {
    -		method "PUT"
    -		url "/multipart"
    -		headers {
    -			contentType('multipart/form-data;boundary=AaB03x')
    -		}
    -		multipart(
    -				file: named(
    -						name: value(stub(regex('.+')), test('file')),
    -						content: value(stub(regex('.+')), test([100, 117, 100, 97] as byte[]))
    -				)
    -		)
    -	}
    -	response {
    -		status 200
    -	}
    -}

    +

    YAML. 

    request:
       method: PUT
    @@ -4009,27 +3944,7 @@ need to use patterns and not exact values both for your test and your server sid
     	}
     }

    You can also provide only one side of the communication with a regular expression. If you do so, then the contract engine automatically provides the generated string that matches -the provided regular expression. The following code shows an example:

    org.springframework.cloud.contract.spec.Contract.make {
    -	request {
    -		method 'PUT'
    -		url value(consumer(regex('/foo/[0-9]{5}')))
    -		body([
    -			requestElement: $(consumer(regex('[0-9]{5}')))
    -		])
    -		headers {
    -			header('header', $(consumer(regex('application\\/vnd\\.fraud\\.v1\\+json;.*'))))
    -		}
    -	}
    -	response {
    -		status OK()
    -		body([
    -			responseElement: $(producer(regex('[0-9]{7}')))
    -		])
    -		headers {
    -			contentType("application/vnd.fraud.v1+json")
    -		}
    -	}
    -}

    In the preceding example, the opposite side of the communication has the respective data +the provided regular expression. The following code shows an example:

    In the preceding example, the opposite side of the communication has the respective data generated for request and response.

    Spring Cloud Contract comes with a series of predefined regular expressions that you can use in your contracts, as shown in the following example:

    protected static final Pattern TRUE_OR_FALSE = Pattern.compile(/(true|false)/)
     protected static final Pattern ALPHA_NUMERIC = Pattern.compile('[a-zA-Z0-9]+')
    @@ -4130,30 +4045,7 @@ Pattern nonEmpty() {
     
     Pattern nonBlank() {
     	return NON_BLANK
    -}

    In your contract, you can use it as shown in the following example:

    Contract dslWithOptionalsInString = Contract.make {
    -	priority 1
    -	request {
    -		method POST()
    -		url '/users/password'
    -		headers {
    -			contentType(applicationJson())
    -		}
    -		body(
    -				email: $(consumer(optional(regex(email()))), producer('abc@abc.com')),
    -				callback_url: $(consumer(regex(hostname())), producer('http://partners.com'))
    -		)
    -	}
    -	response {
    -		status 404
    -		headers {
    -			contentType(applicationJson())
    -		}
    -		body(
    -				code: value(consumer("123123"), producer(optional("123123"))),
    -				message: "User not found by email = [${value(producer(regex(email())), consumer('not.existing@user.com'))}]"
    -		)
    -	}
    -}

    8.5.3 Passing Optional Parameters

    [Important]Important

    This section is valid only for Groovy DSL. Check out the +}

    In your contract, you can use it as shown in the following example:

    8.5.3 Passing Optional Parameters

    [Important]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:

    • STUB side of the Request
    • TEST side of the Response

    The following example shows how to provide optional parameters:

    org.springframework.cloud.contract.spec.Contract.make {
     	priority 1
    @@ -4263,16 +4155,16 @@ ensure that the authToken() method returns everythi
     JSON path:

    • String: If you point to a String value in the JSON.
    • JSONArray: If you point to a List in the JSON.
    • Map: If you point to a Map in the JSON.
    • Number: If you point to Integer, Double etc. in the JSON.
    • Boolean: If you point to a Boolean in the JSON.

    In the request part of the contract, you can specify that the body should be taken from a method.

    [Important]Important

    You must provide both the consumer and the producer side. The execute part is applied for the whole body - not for parts of it.

    The following example shows how to read an object from JSON:

    Contract contractDsl = Contract.make {
    -	request {
    -		method 'GET'
    -		url '/something'
    -		body(
    -				$(c("foo"), p(execute("hashCode()")))
    -		)
    -	}
    -	response {
    -		status OK()
    -	}
    +    request {
    +        method 'GET'
    +        url '/something'
    +        body(
    +                $(c('foo'), p(execute('hashCode()')))
    +        )
    +    }
    +    response {
    +        status OK()
    +    }
     }

    The preceding example results in calling the hashCode() method in the request body. It should resemble the following code:

    // given:
      MockMvcRequestSpecification request = given()
    @@ -4294,80 +4186,7 @@ matches the JSON Path.

    If you’re using the YAML contract 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') {
    -			queryParameters {
    -				parameter("foo", "bar")
    -				parameter("foo", "bar2")
    -			}
    -		}
    -		headers {
    -			header(authorization(), "secret")
    -			header(authorization(), "secret2")
    -		}
    -		body(foo: "bar", baz: 5)
    -	}
    -	response {
    -		status OK()
    -		headers {
    -			header(authorization(), "foo ${fromRequest().header(authorization())} bar")
    -		}
    -		body(
    -				url: fromRequest().url(),
    -				path: fromRequest().path(),
    -				pathIndex: fromRequest().path(1),
    -				param: fromRequest().query("foo"),
    -				paramIndex: fromRequest().query("foo", 1),
    -				authorization: fromRequest().header("Authorization"),
    -				authorization2: fromRequest().header("Authorization", 1),
    -				fullBody: fromRequest().body(),
    -				responseFoo: fromRequest().body('$.foo'),
    -				responseBaz: fromRequest().body('$.baz'),
    -				responseBaz2: "Bla bla ${fromRequest().body('$.foo')} bla bla",
    -				rawUrl: fromRequest().rawUrl(),
    -				rawPath: fromRequest().rawPath(),
    -				rawPathIndex: fromRequest().rawPath(1),
    -				rawParam: fromRequest().rawQuery("foo"),
    -				rawParamIndex: fromRequest().rawQuery("foo", 1),
    -				rawAuthorization: fromRequest().rawHeader("Authorization"),
    -				rawAuthorization2: fromRequest().rawHeader("Authorization", 1),
    -				rawResponseFoo: fromRequest().rawBody('$.foo'),
    -				rawResponseBaz: fromRequest().rawBody('$.baz'),
    -				rawResponseBaz2: "Bla bla ${fromRequest().rawBody('$.foo')} bla bla"
    -		)
    -	}
    -}
    -Contract contractDsl = Contract.make {
    -	request {
    -		method 'GET'
    -		url('/api/v1/xxxx') {
    -			queryParameters {
    -				parameter("foo", "bar")
    -				parameter("foo", "bar2")
    -			}
    -		}
    -		headers {
    -			header(authorization(), "secret")
    -			header(authorization(), "secret2")
    -		}
    -		body(foo: "bar", baz: 5)
    -	}
    -	response {
    -		status OK()
    -		headers {
    -			contentType(applicationJson())
    -		}
    -		body('''
    -				{
    -					"responseFoo": "{{{ jsonPath request.body '$.foo' }}}",
    -					"responseBaz": {{{ jsonPath request.body '$.baz' }}},
    -					"responseBaz2": "Bla bla {{{ jsonPath request.body '$.foo' }}} bla bla"
    -				}
    -		'''.toString())
    -	}
    -}

    +

    YAML. 

    request:
       method: GET
    @@ -4537,122 +4356,122 @@ email, url, uuid, iso_date, iso_date_time, iso_time, iso_8601_with_offset, non_e
       type: by_regex
       predefined: only_alpha_unicode

    Below you can find the allowed list of `type`s.

    • For stubMatchers:

      • by_equality
      • by_regex
      • by_date
      • by_timestamp
      • by_time
    • For testMatchers:

      • by_equality
      • by_regex
      • by_date
      • by_timestamp
      • by_time
      • by_type

        • there are 2 additional fields accepted: minOccurrence and maxOccurrence.
      • by_command
      • by_null

    Consider the following example:

    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}"))
    -			jsonPath('$.duck', byEquality())
    -			jsonPath('$.alpha', byRegex(onlyAlphaUnicode()))
    -			jsonPath('$.alpha', byEquality())
    -			jsonPath('$.number', byRegex(number()))
    -			jsonPath('$.aBoolean', byRegex(anyBoolean()))
    -			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}"))
    -			// asserts the jsonpath value against the provided value
    -			jsonPath('$.duck', byEquality())
    -			// asserts the jsonpath value against some default regex
    -			jsonPath('$.alpha', byRegex(onlyAlphaUnicode()))
    -			jsonPath('$.alpha', byEquality())
    -			jsonPath('$.number', byRegex(number()))
    -			jsonPath('$.positiveInteger', byRegex(anInteger()))
    -			jsonPath('$.negativeInteger', byRegex(anInteger()))
    -			jsonPath('$.positiveDecimalNumber', byRegex(aDouble()))
    -			jsonPath('$.negativeDecimalNumber', byRegex(aDouble()))
    -			jsonPath('$.aBoolean', byRegex(anyBoolean()))
    -			// 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}'))))
    -		}
    -	}
    +    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}"))
    +            jsonPath('$.duck', byEquality())
    +            jsonPath('$.alpha', byRegex(onlyAlphaUnicode()))
    +            jsonPath('$.alpha', byEquality())
    +            jsonPath('$.number', byRegex(number()))
    +            jsonPath('$.aBoolean', byRegex(anyBoolean()))
    +            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}"))
    +            // asserts the jsonpath value against the provided value
    +            jsonPath('$.duck', byEquality())
    +            // asserts the jsonpath value against some default regex
    +            jsonPath('$.alpha', byRegex(onlyAlphaUnicode()))
    +            jsonPath('$.alpha', byEquality())
    +            jsonPath('$.number', byRegex(number()))
    +            jsonPath('$.positiveInteger', byRegex(anInteger()))
    +            jsonPath('$.negativeInteger', byRegex(anInteger()))
    +            jsonPath('$.positiveDecimalNumber', byRegex(aDouble()))
    +            jsonPath('$.negativeDecimalNumber', byRegex(aDouble()))
    +            jsonPath('$.aBoolean', byRegex(anyBoolean()))
    +            // 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:
    @@ -5995,13 +5814,13 @@ to clone the repository and use it as a source of contracts
     to generate tests or stubs.

    Either via environment variables, system properties, properties set inside the plugin or contracts repository configuration you can tweak the downloader’s behaviour. Below you can find the list of -properties

    Table 10.1. SCM Stub Downloader properties

    Type of a property

    Name of the property

    Description

    * git.branch (plugin prop)

    * stubrunner.properties.git.branch (system prop)

    * STUBRUNNER_PROPERTIES_GIT_BRANCH (env prop)

    master

    Which branch to checkout

    * git.username (plugin prop)

    * stubrunner.properties.git.username (system prop)

    * STUBRUNNER_PROPERTIES_GIT_USERNAME (env prop)

     

    Git clone username

    * git.password (plugin prop)

    * stubrunner.properties.git.password (system prop)

    * STUBRUNNER_PROPERTIES_GIT_PASSWORD (env prop)

     

    Git clone password

    * git.no-of-attempts (plugin prop)

    * stubrunner.properties.git.no-of-attempts (system prop)

    * STUBRUNNER_PROPERTIES_GIT_NO_OF_ATTEMPTS (env prop)

    10

    Number of attempts to push the commits to origin

    * git.wait-between-attempts (Plugin prop)

    * stubrunner.properties.git.wait-between-attempts (system prop)

    * STUBRUNNER_PROPERTIES_GIT_WAIT_BETWEEN_ATTEMPTS (env prop)

    1000

    Number of millis to wait between attempts to push the commits to origin


    10.7 Using the Pact Stub Downloader

    Whenever the repositoryRoot starts with a Pact protocol +properties

    Table 10.1. SCM Stub Downloader properties

    Type of a property

    Name of the property

    Description

    * git.branch (plugin prop)

    * stubrunner.properties.git.branch (system prop)

    * STUBRUNNER_PROPERTIES_GIT_BRANCH (env prop)

    master

    Which branch to checkout

    * git.username (plugin prop)

    * stubrunner.properties.git.username (system prop)

    * STUBRUNNER_PROPERTIES_GIT_USERNAME (env prop)

     

    Git clone username

    * git.password (plugin prop)

    * stubrunner.properties.git.password (system prop)

    * STUBRUNNER_PROPERTIES_GIT_PASSWORD (env prop)

     

    Git clone password

    * git.no-of-attempts (plugin prop)

    * stubrunner.properties.git.no-of-attempts (system prop)

    * STUBRUNNER_PROPERTIES_GIT_NO_OF_ATTEMPTS (env prop)

    10

    Number of attempts to push the commits to origin

    * git.wait-between-attempts (Plugin prop)

    * stubrunner.properties.git.wait-between-attempts (system prop)

    * STUBRUNNER_PROPERTIES_GIT_WAIT_BETWEEN_ATTEMPTS (env prop)

    1000

    Number of millis to wait between attempts to push the commits to origin


    10.7 Using the Pact Stub Downloader

    Whenever the repositoryRoot starts with a Pact protocol (starts with pact://), the stub downloader will try to fetch the Pact contract definitions from the Pact Broker. Whatever is set after pact:// will be parsed as the Pact Broker URL.

    Either via environment variables, system properties, properties set inside the plugin or contracts repository configuration you can tweak the downloader’s behaviour. Below you can find the list of -properties

    Table 10.2. SCM Stub Downloader properties

    Name of a property

    Default

    Description

    * pactbroker.host (plugin prop)

    * stubrunner.properties.pactbroker.host (system prop)

    * STUBRUNNER_PROPERTIES_PACTBROKER_HOST (env prop)

    Host from URL passed to repositoryRoot

    What is the URL of Pact Broker

    * pactbroker.port (plugin prop)

    * stubrunner.properties.pactbroker.port (system prop)

    * STUBRUNNER_PROPERTIES_PACTBROKER_PORT (env prop)

    Port from URL passed to repositoryRoot

    What is the port of Pact Broker

    * pactbroker.protocol (plugin prop)

    * stubrunner.properties.pactbroker.protocol (system prop)

    * STUBRUNNER_PROPERTIES_PACTBROKER_PROTOCOL (env prop)

    Protocol from URL passed to repositoryRoot

    What is the protocol of Pact Broker

    * pactbroker.tags (plugin prop)

    * stubrunner.properties.pactbroker.tags (system prop)

    * STUBRUNNER_PROPERTIES_PACTBROKER_TAGS (env prop)

    Version of the stub, or latest if version is +

    What tags should be used to fetch the stub

    * pactbroker.auth.scheme (plugin prop)

    * stubrunner.properties.pactbroker.auth.scheme (system prop)

    * STUBRUNNER_PROPERTIES_PACTBROKER_AUTH_SCHEME (env prop)

    Basic

    What kind of authentication should be used to connect to the Pact Broker

    * pactbroker.auth.username (plugin prop)

    * stubrunner.properties.pactbroker.auth.username (system prop)

    * STUBRUNNER_PROPERTIES_PACTBROKER_AUTH_USERNAME (env prop)

    The username passed to contractsRepositoryUsername (maven) or contractRepository.username (gradle)

    Username used to connect to the Pact Broker

    * pactbroker.auth.password (plugin prop)

    * stubrunner.properties.pactbroker.auth.password (system prop)

    * STUBRUNNER_PROPERTIES_PACTBROKER_AUTH_PASSWORD (env prop)

    The password passed to contractsRepositoryPassword (maven) or contractRepository.password (gradle)

    Password used to connect to the Pact Broker

    * pactbroker.provider-name-with-group-id (plugin prop)

    * stubrunner.properties.pactbroker.provider-name-with-group-id (system prop)

    * STUBRUNNER_PROPERTIES_PACTBROKER_PROVIDER_NAME_WITH_GROUP_ID (env prop)

    false

    When true, the provider name will be a combination of groupId:artifactId. If false, just artifactId is used


    11. Spring Cloud Contract WireMock

    The Spring Cloud Contract WireMock modules let you use WireMock in a +properties

    Table 10.2. SCM Stub Downloader properties

    Name of a property

    Default

    Description

    * pactbroker.host (plugin prop)

    * stubrunner.properties.pactbroker.host (system prop)

    * STUBRUNNER_PROPERTIES_PACTBROKER_HOST (env prop)

    Host from URL passed to repositoryRoot

    What is the URL of Pact Broker

    * pactbroker.port (plugin prop)

    * stubrunner.properties.pactbroker.port (system prop)

    * STUBRUNNER_PROPERTIES_PACTBROKER_PORT (env prop)

    Port from URL passed to repositoryRoot

    What is the port of Pact Broker

    * pactbroker.protocol (plugin prop)

    * stubrunner.properties.pactbroker.protocol (system prop)

    * STUBRUNNER_PROPERTIES_PACTBROKER_PROTOCOL (env prop)

    Protocol from URL passed to repositoryRoot

    What is the protocol of Pact Broker

    * pactbroker.tags (plugin prop)

    * stubrunner.properties.pactbroker.tags (system prop)

    * STUBRUNNER_PROPERTIES_PACTBROKER_TAGS (env prop)

    Version of the stub, or latest if version is +

    What tags should be used to fetch the stub

    * pactbroker.auth.scheme (plugin prop)

    * stubrunner.properties.pactbroker.auth.scheme (system prop)

    * STUBRUNNER_PROPERTIES_PACTBROKER_AUTH_SCHEME (env prop)

    Basic

    What kind of authentication should be used to connect to the Pact Broker

    * pactbroker.auth.username (plugin prop)

    * stubrunner.properties.pactbroker.auth.username (system prop)

    * STUBRUNNER_PROPERTIES_PACTBROKER_AUTH_USERNAME (env prop)

    The username passed to contractsRepositoryUsername (maven) or contractRepository.username (gradle)

    Username used to connect to the Pact Broker

    * pactbroker.auth.password (plugin prop)

    * stubrunner.properties.pactbroker.auth.password (system prop)

    * STUBRUNNER_PROPERTIES_PACTBROKER_AUTH_PASSWORD (env prop)

    The password passed to contractsRepositoryPassword (maven) or contractRepository.password (gradle)

    Password used to connect to the Pact Broker

    * pactbroker.provider-name-with-group-id (plugin prop)

    * stubrunner.properties.pactbroker.provider-name-with-group-id (system prop)

    * STUBRUNNER_PROPERTIES_PACTBROKER_PROVIDER_NAME_WITH_GROUP_ID (env prop)

    false

    When true, the provider name will be a combination of groupId:artifactId. If false, just artifactId is used


    11. Spring Cloud Contract WireMock

    The Spring Cloud Contract WireMock modules let you use WireMock in a Spring Boot application. Check out the samples for more details.

    If you have a Spring Boot application that uses Tomcat as an embedded server (which is @@ -6163,8 +5982,7 @@ Example:

    		

    11.7 Generating Stubs using REST Docs

    Spring REST Docs can be used to generate documentation (for example in Asciidoctor format) for an HTTP API with Spring MockMvc -or WebTestClient or -Rest Assured. At the same time that you generate documentation for your API, you can also +or WebTestClient or Rest Assured. At the same time that you generate documentation for your API, you can also generate WireMock stubs by using Spring Cloud Contract WireMock. To do so, write your normal REST Docs test cases and use @AutoConfigureRestDocs to have stubs be automatically generated in the REST Docs output directory. The following code shows an diff --git a/spring-cloud-contract.xml b/spring-cloud-contract.xml index 19b137e773..5bccdd6221 100644 --- a/spring-cloud-contract.xml +++ b/spring-cloud-contract.xml @@ -4,7 +4,7 @@ Spring Cloud Contract -2018-10-22 +2018-10-19 @@ -410,9 +410,32 @@ public void validate_shouldMarkClientAsFraud() throws Exception { assertThatJson(parsedJson).field("['rejection.reason']").isEqualTo("Amount too high"); } The preceding example uses Spring’s MockMvc to run the tests. This is the default test -mode for HTTP contracts. However, JAX-RX client and explicit HTTP invocations can also be +mode for HTTP contracts. However, JAX-RS client and explicit HTTP invocations can also be used. (To do so, change the testMode property of the plugin to JAX-RS or EXPLICIT, respectively.) +Since 2.1.0, it is also possible to use RestAssuredWebTestClient`with Spring’s reactive `WebTestClient +run under the hood. This is particularly recommended while working with Reactive, Web-Flux-based applications. +In order to use WebTestClient set testMode to WEBTESTCLIENT. +Here is an example of a test generated in WEBTESTCLIENT test mode: +[source,java,indent=0] +@Test + public void validate_shouldRejectABeerIfTooYoung() throws Exception { + // given: + WebTestClientRequestSpecification request = given() + .header("Content-Type", "application/json") + .body("{\"age\":10}"); + + // when: + WebTestClientResponse response = given().spec(request) + .post("/check"); + + // 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("['status']").isEqualTo("NOT_OK"); + } Apart from the default JUnit 4, you can instead use JUnit 5 or Spock tests, by setting the plugin testFramework property to either JUNIT5 or Spock. @@ -2534,7 +2557,7 @@ shown here: testMode: Defines the mode for acceptance tests. By default, the mode is MockMvc, -which is based on Spring’s MockMvc. It can also be changed to JaxRsClient or to +which is based on Spring’s MockMvc. It can also be changed to WebTestClient, JaxRsClient or to Explicit for real HTTP calls. @@ -2968,7 +2991,7 @@ definition or the execution definition, as shown here: testMode: Defines the mode for acceptance tests. By default, the mode is MockMvc, -which is based on Spring’s MockMvc. It can also be changed to JaxRsClient or to +which is based on Spring’s MockMvc. It can also be changed to WebTestClient, JaxRsClient or to Explicit for real HTTP calls. @@ -5828,48 +5851,7 @@ the Contract class: import org.springframework.cloud 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' - headers { - header 'Content-Type': 'application/vnd.org.springframework.cloud.contract.verifier.twitter-places-analyzer.v1+json' - } - body '''\ - [{ - "created_at": "Sat Jul 26 09:38:57 +0000 2014", - "id": 492967299297845248, - "id_str": "492967299297845248", - "text": "Gonna see you at Warsaw", - "place": - { - "attributes":{}, - "bounding_box": - { - "coordinates": - [[ - [-77.119759,38.791645], - [-76.909393,38.791645], - [-76.909393,38.995548], - [-77.119759,38.995548] - ]], - "type":"Polygon" - }, - "country":"United States", - "country_code":"US", - "full_name":"Washington, DC", - "id":"01fbe706f872cb32", - "name":"Washington", - "place_type":"city", - "url": "http://api.twitter.com/1/geo/id/01fbe706f872cb32.json" - } - }] - ''' - } - response { - status OK() - } -} + The following is a complete example of a YAML contract definition: description: Some description name: some name @@ -6446,50 +6428,7 @@ body: Groovy DSL -org.springframework.cloud.contract.spec.Contract contractDsl = org.springframework.cloud.contract.spec.Contract.make { - request { - method "PUT" - url "/multipart" - headers { - contentType('multipart/form-data;boundary=AaB03x') - } - multipart( - // key (parameter name), value (parameter value) pair - formParameter: $(c(regex('".+"')), p('"formParameterValue"')), - someBooleanParameter: $(c(regex(anyBoolean())), p('true')), - // a named parameter (e.g. with `file` name) that represents file with - // `name` and `content`. You can also call `named("fileName", "fileContent")` - file: named( - // name of the file - name: $(c(regex(nonEmpty())), p('filename.csv')), - // content of the file - content: $(c(regex(nonEmpty())), p('file content')), - // content type for the part - contentType: $(c(regex(nonEmpty())), p('application/json'))) - ) - } - response { - status OK() - } -} -org.springframework.cloud.contract.spec.Contract contractDsl = org.springframework.cloud.contract.spec.Contract.make { - request { - method "PUT" - url "/multipart" - headers { - contentType('multipart/form-data;boundary=AaB03x') - } - multipart( - file: named( - name: value(stub(regex('.+')), test('file')), - content: value(stub(regex('.+')), test([100, 117, 100, 97] as byte[])) - ) - ) - } - response { - status 200 - } -} + @@ -6714,27 +6653,7 @@ need to use patterns and not exact values both for your test and your server sid You can also provide only one side of the communication with a regular expression. If you do so, then the contract engine automatically provides the generated string that matches the provided regular expression. The following code shows an example: -org.springframework.cloud.contract.spec.Contract.make { - request { - method 'PUT' - url value(consumer(regex('/foo/[0-9]{5}'))) - body([ - requestElement: $(consumer(regex('[0-9]{5}'))) - ]) - headers { - header('header', $(consumer(regex('application\\/vnd\\.fraud\\.v1\\+json;.*')))) - } - } - response { - status OK() - body([ - responseElement: $(producer(regex('[0-9]{7}'))) - ]) - headers { - contentType("application/vnd.fraud.v1+json") - } - } -} + In the preceding example, the opposite side of the communication has the respective data generated for request and response. Spring Cloud Contract comes with a series of predefined regular expressions that you can @@ -6840,30 +6759,7 @@ Pattern nonBlank() { return NON_BLANK } In your contract, you can use it as shown in the following example: -Contract dslWithOptionalsInString = Contract.make { - priority 1 - request { - method POST() - url '/users/password' - headers { - contentType(applicationJson()) - } - body( - email: $(consumer(optional(regex(email()))), producer('abc@abc.com')), - callback_url: $(consumer(regex(hostname())), producer('http://partners.com')) - ) - } - response { - status 404 - headers { - contentType(applicationJson()) - } - body( - code: value(consumer("123123"), producer(optional("123123"))), - message: "User not found by email = [${value(producer(regex(email())), consumer('not.existing@user.com'))}]" - ) - } -} +

    Passing Optional Parameters @@ -7032,16 +6928,16 @@ is applied for the whole body - not for parts of it. The following example shows how to read an object from JSON: Contract contractDsl = Contract.make { - request { - method 'GET' - url '/something' - body( - $(c("foo"), p(execute("hashCode()"))) - ) - } - response { - status OK() - } + request { + method 'GET' + url '/something' + body( + $(c('foo'), p(execute('hashCode()'))) + ) + } + response { + status OK() + } } The preceding example results in calling the hashCode() method in the request body. It should resemble the following code: @@ -7130,80 +7026,7 @@ matches the JSON Path. E.g. for json path $.foo - {{ Groovy DSL -Contract contractDsl = Contract.make { - request { - method 'GET' - url('/api/v1/xxxx') { - queryParameters { - parameter("foo", "bar") - parameter("foo", "bar2") - } - } - headers { - header(authorization(), "secret") - header(authorization(), "secret2") - } - body(foo: "bar", baz: 5) - } - response { - status OK() - headers { - header(authorization(), "foo ${fromRequest().header(authorization())} bar") - } - body( - url: fromRequest().url(), - path: fromRequest().path(), - pathIndex: fromRequest().path(1), - param: fromRequest().query("foo"), - paramIndex: fromRequest().query("foo", 1), - authorization: fromRequest().header("Authorization"), - authorization2: fromRequest().header("Authorization", 1), - fullBody: fromRequest().body(), - responseFoo: fromRequest().body('$.foo'), - responseBaz: fromRequest().body('$.baz'), - responseBaz2: "Bla bla ${fromRequest().body('$.foo')} bla bla", - rawUrl: fromRequest().rawUrl(), - rawPath: fromRequest().rawPath(), - rawPathIndex: fromRequest().rawPath(1), - rawParam: fromRequest().rawQuery("foo"), - rawParamIndex: fromRequest().rawQuery("foo", 1), - rawAuthorization: fromRequest().rawHeader("Authorization"), - rawAuthorization2: fromRequest().rawHeader("Authorization", 1), - rawResponseFoo: fromRequest().rawBody('$.foo'), - rawResponseBaz: fromRequest().rawBody('$.baz'), - rawResponseBaz2: "Bla bla ${fromRequest().rawBody('$.foo')} bla bla" - ) - } -} -Contract contractDsl = Contract.make { - request { - method 'GET' - url('/api/v1/xxxx') { - queryParameters { - parameter("foo", "bar") - parameter("foo", "bar2") - } - } - headers { - header(authorization(), "secret") - header(authorization(), "secret2") - } - body(foo: "bar", baz: 5) - } - response { - status OK() - headers { - contentType(applicationJson()) - } - body(''' - { - "responseFoo": "{{{ jsonPath request.body '$.foo' }}}", - "responseBaz": {{{ jsonPath request.body '$.baz' }}}, - "responseBaz2": "Bla bla {{{ jsonPath request.body '$.foo' }}} bla bla" - } - '''.toString()) - } -} + @@ -7561,122 +7384,122 @@ email, url, uuid, iso_date, iso_date_time, iso_time, iso_8601_with_offset, non_e 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}")) - jsonPath('$.duck', byEquality()) - jsonPath('$.alpha', byRegex(onlyAlphaUnicode())) - jsonPath('$.alpha', byEquality()) - jsonPath('$.number', byRegex(number())) - jsonPath('$.aBoolean', byRegex(anyBoolean())) - 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}")) - // asserts the jsonpath value against the provided value - jsonPath('$.duck', byEquality()) - // asserts the jsonpath value against some default regex - jsonPath('$.alpha', byRegex(onlyAlphaUnicode())) - jsonPath('$.alpha', byEquality()) - jsonPath('$.number', byRegex(number())) - jsonPath('$.positiveInteger', byRegex(anInteger())) - jsonPath('$.negativeInteger', byRegex(anInteger())) - jsonPath('$.positiveDecimalNumber', byRegex(aDouble())) - jsonPath('$.negativeDecimalNumber', byRegex(aDouble())) - jsonPath('$.aBoolean', byRegex(anyBoolean())) - // 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}')))) - } - } + 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}")) + jsonPath('$.duck', byEquality()) + jsonPath('$.alpha', byRegex(onlyAlphaUnicode())) + jsonPath('$.alpha', byEquality()) + jsonPath('$.number', byRegex(number())) + jsonPath('$.aBoolean', byRegex(anyBoolean())) + 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}")) + // asserts the jsonpath value against the provided value + jsonPath('$.duck', byEquality()) + // asserts the jsonpath value against some default regex + jsonPath('$.alpha', byRegex(onlyAlphaUnicode())) + jsonPath('$.alpha', byEquality()) + jsonPath('$.number', byRegex(number())) + jsonPath('$.positiveInteger', byRegex(anInteger())) + jsonPath('$.negativeInteger', byRegex(anInteger())) + jsonPath('$.positiveDecimalNumber', byRegex(aDouble())) + jsonPath('$.negativeDecimalNumber', byRegex(aDouble())) + jsonPath('$.aBoolean', byRegex(anyBoolean())) + // 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}')))) + } + } } @@ -9701,8 +9524,7 @@ Example: Generating Stubs using REST Docs Spring REST Docs can be used to generate documentation (for example in Asciidoctor format) for an HTTP API with Spring MockMvc -or WebTestClient or -Rest Assured. At the same time that you generate documentation for your API, you can also +or WebTestClient or Rest Assured. At the same time that you generate documentation for your API, you can also generate WireMock stubs by using Spring Cloud Contract WireMock. To do so, write your normal REST Docs test cases and use @AutoConfigureRestDocs to have stubs be automatically generated in the REST Docs output directory. The following code shows an