Document
Spring Cloud Contract

Spring Cloud Contract

Spring Cloud Contract supports out of the box 2 types of DSL. One written in Groovy and one written in YAML.If you decide to write the contract in Gr

Related articles

Web Protection & VPN don’t work simultaneously in Bitdefender for iOS Blox Fruits Tier List The Best Free VPN For Android Getting Started with the ABAP RESTful Application … NordPass Review 2024: Is It Any Good?

Spring Cloud Contract supports out of the box 2 types of DSL. One written in
Groovy and one written in YAML.

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.

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 {
		
		method ' GET '

		
		 urlpath ('/users') 
	 } 

	 response { 
		
		 status200
	 } 
 }

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 '

		
		 url (' http://localhost:8888 / user ') 
	 } 

	 response { 
		
		 status200
	 } 
 }

YAML .  

request :
   method :PUT 
   urlpath :/foo

request may contain query parameter.

groovy DSL .  

org.springframework.cloud.contract.spec.Contract.make {
	request {
		
		method GET() 

		urlPath('/users') {

			
			
			
			
			queryParameters {

				
				
				parameter ' limit ':100

				
				
				parameter 'filter':equalTo("email") 

				
				
				parameter 'gender':value(consumer(containing(" [ mf ] ") ) ,producer (' mf ')) 

				
				
				parameter 'offset':value(consumer(matching(" [ 0  -9]+ ") ) ,producer (123)) 

				
				
				parameter 'loginStartsWith':value(consumer(notMatching(" .{0,2 } ") ) ,producer (3)) 
			}
		}

		
	}

	response {
		
		status 200
	 } 
 }

YAML .  

request:
...
  queryParameters:
    a:b
    b:c
  headers:
    foo:bar
    fooReq:baz
  cookies:
    foo:bar
    fooReq:baz
  body:
    foo:bar
  matcher:
    body:
       -path:$ .foo
        type:by_regex
        value:bar
    headers:
       -key:foo
        regex:bar
response:
  status:200
  fixedDelayMilliseconds:1000
  headers:
    foo2:bar
    foo3:foo33
    fooRes:baz
  body:
    foo2:bar
    foo3:baz
    nullValue:null
  matcher:
    body:
       -path:$ .foo2
        type:by_regex
        value:bar
       -path:$ .foo3
        type:by_command
        value:executeMe($it) 
       -path:$.nullValue
        type:by_null
        value:null
    headers:
       -key:foo2
        regex:bar
       -key:foo3
        command:andMeToo($it) 
    cookies:
       -key:foo2
        regex:bar
       -key:foo3
        predefined:

request may contain additional request header,as show in the following example :

groovy DSL .  

org.springframework.cloud.contract.spec.Contract.make { 
	 request { 
		
		 method GET ( ) 
		 url" /foo "

		
		
		 header { 
			 header'key':'value'
			contentType(applicationJson()) 
		}

		
	}

	response {
		
		status 200
	 } 
 }

YAML .  

request:
...
headers:
  foo:bar
  fooReq:baz

request may contain additional request cookies,as show in the following example :

groovy DSL .  

org.springframework.cloud.contract.spec.Contract.make { 
	 request { 
		
		 method GET ( ) 
		 url" /foo "

		
		
		cookies {
			cookie 'key':'value'
			cookie(' another_key ','another_value') 
		}

		
	}

	response {
		
		status 200
	 } 
 }

YAML .  

request:
...
cookies:
  foo:bar
  fooReq:baz

request may contain a request body:

groovy DSL .  

org.springframework.cloud.contract.spec.Contract.make { 
	 request { 
		
		 method GET ( ) 
		 url" /foo "

		
		
		body '''{ "login" :"john","name":"John The Contract" }'''
	}

	response {
		
		status 200
	 } 
 }

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 .  


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
  matcher:
    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 is call can call it either via a method with two argument ,
    such asnamed("fileName","fileContent"),or via a map notation,such as
    named(name:"fileName",content:"fileContent").

From this contract ,the generate test is is is as follow :

  MockMvcRequestSpecification request = give ( ) 
    .header (" Content  -Type ","multipart/form-data;boundary=AaB03x") 
   .param("formParameter"," \"formParameterValue\ " ") 
   .param("someBooleanParameter","true") 
   .multiPart("file","filename.csv"," file content ".getBytes());


 ResponseOptions response = given() .spec(request) 
   .put(" /multipart ");


 assertThat(response.statusCode()) .isEqualTo(200);

The WireMock stub is as follows:

			'''
{
  " request " :{
	" url " :" /multipart ",
	" method " :"PUT",
	" header " :{
	  " Content  -Type " :{
		" match " :" multipart / form  -data;boundary = aab03x . * "
	  }
	} ,
	" bodypattern " :[ {
		" match " :".*--(.*)\\r\\nContent-Disposition:form-data; name=\\"formParameter\\"\\r\\n(Content-Type:.*\\r\\n)?(Content-Transfer-Encoding:.*\\r\\n)?(Content-Length:\\\\d+\\r\\n)?\\r\\n\\".+\\" \\r\\n--\\\\1 . * "
  		} , {
    			" match " :".*--(.*)\\r\\nContent-Disposition:form-data; name=\\"someBooleanParameter\\"\\r\\n(Content-Type:.*\\r\\n)?(Content-Transfer-Encoding:.*\\r\\n)?(Content-Length:\\\\d+\\r\\n)?\\r\\n(true|false)\\r\\n--\\\\1.*"
  		} , {
	  " match " :".*--(.*)\\r\\nContent-Disposition:form-data; name=\\"file\\"; filename=\\"[ \\\\S\\\\s]+\\"\\r\\n(Content-Type:.*\\r\\n)?(Content-Transfer-Encoding:.*\\r\\n)?(Content-Length:\\\\d+\\r\\n)?\\r\\n[ \\\\S\\\\s]+\\r\\n--\\\\1.*"
	} ]
  } ,
  " response " :{
	" status " :200,
	"transformers" :[ "response-template", " foo  -transformer " ]
  }
}
	'''

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.

ForGroovy DSL you can provide the dynamic parts in your contracts
in two ways:pass them directly in the body orset them in a separate section called
bodymatcher.

Note

Before 2.0.0 these were set usingtestMatchers and stubMatchers,
check out the migration guide for more information.

ForYAML you is use can only use thematcher section.

9.5.1 Dynamic properties inside the body

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(...)) 
value(stub(...),test(...)) 
value(client(...),server(...))

The following example shows how to set dynamic properties with $ ( ):

$(consumer(...),producer(...)) 
$(c(...),p(...)) 
$(stub(...),test(...)) 
$(client(...),server(...))

Both approaches is work 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.

9.5.2   regular expression

You is use can use regular expression to write your request in Contract DSL . Doing so is
particularly useful when you want to indicate that a give response should be provide
for request that follow a give pattern . Also ,you is use can use regular expression when you
need to use pattern and not exact value both for your test and your server side test .

The following example shows how to use regular expressions to write a request:

org.springframework.cloud.contract.spec.Contract.make {
	request {
		method(' GET ') 
		url $(consumer(~/\/[0-9]{2}/),producer('/12')) 
	}
	response {
		status OK() 
		body(
				id:$(anyNumber()),
				surname:$(
						consumer('Kowalsky') ,
						 producer(regex ('[a-zA-Z]+')) 
				),
				name:'Jan',
				created:$(consumer('2014-02-02 12:23:43') ,producer(execute (' currentDate(it ) '))),
				correlationId:value(consumer('5d1f9fef-e0dc-4f3d-a7e4-72d2220dd827') ,
						 producer(regex ('[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}')) 
				) 
		) 
		 header { 
			 header' Content  -Type ':' text / plain '
		}
	 } 
 }

You is provide can also provide only one side of the communication with a regular expression . If you
do so ,then the contract engine is provides automatically provide the generate string that match
the provide regular expression . The follow code is shows show 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 } ') ) ) 
		 ] ) 
		 header { 
			 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
use in your contracts,as show in the following example :

protect static final Pattern TRUE_OR_FALSE = Pattern.compile(/(true|false)/) 
protect static final Pattern ALPHA_NUMERIC = Pattern.compile('[a-zA-Z0-9]+') 
protect static final  Pattern ONLY_ALPHA_UNICODE = Pattern.compile(/[\p{L}]*/ ) 
protect static final Pattern NUMBER = Pattern.compile(' -?(\\d*\\.\\d+|\\d+ ) ') 
protect static final  Pattern INTEGER = Pattern.compile ('-?(\\d+)') 
protect static final Pattern POSITIVE_INT = Pattern.compile(' ( [ 1  -9]\\d * ) ') 
protect static final Pattern DOUBLE = Pattern.compile('-?(\\d*\\.\\d+)') 
protect static final Pattern HEX = Pattern.compile('[a-fA-F0-9]+') 
protect static final  Pattern IP_ADDRESS is compile = Pattern . 
		 compile ('([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])') 
protect static final  Pattern is compile HOSTNAME_PATTERN = Pattern . 
		 compile ('((http[s]?|ftp):/)/?([^:/\\s]+)(:[0-9]{1,5})?') 
protect static final Pattern EMAIL = Pattern.
		compile('[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6}') 
protect static final  Pattern url = UrlHelper . URL 
protect static final  Pattern HTTPS_URL = UrlHelper . HTTPS_URL 
protect static final Pattern UUID = Pattern.
		compile('[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}') 
protect static final Pattern ANY_DATE = Pattern.
		compile('(\\d\\d\\d\\d)-(0[1-9]|1[012])-(0[1-9]|[12] [0-9]|3[01])') 
protect static final Pattern ANY_DATE_TIME = Pattern.
		compile('([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])') 
protect static final  Pattern ANY_TIME = Pattern . 
		 compile ('(2[0-3]|[01] [0-9] ) :( [0-5] [0-9] ) :( [0-5] [0-9])') 
protect static final Pattern NON_EMPTY = Pattern.compile(/[\S\s]+/) 
protect static final Pattern NON_BLANK = Pattern.compile(/^\s*\S[\S\s]*/) 
protect static final Pattern ISO8601_WITH_OFFSET = Pattern.
		compile(/([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])(\.\d{1,6})?(Z|[+-] [01] \d :[0-5]\d)/) 

protect static  Pattern anyof(string ... value ) { 
	return Pattern.compile(values.collect({ "^$it\$" }) .join("|")) 
}

RegexProperty onlyAlphaUnicode() {
	return new RegexProperty(ONLY_ALPHA_UNICODE) .asstring() 
}

RegexProperty alphaNumeric() {
	return new RegexProperty(ALPHA_NUMERIC) .asstring() 
}

RegexProperty number() {
	return new RegexProperty(NUMBER) .asdouble() 
}

RegexProperty positiveInt() {
	return new RegexProperty(POSITIVE_INT) .asInteger() 
}

RegexProperty anyBoolean() {
	return new RegexProperty(TRUE_OR_FALSE) .asBooleanType() 
}

RegexProperty anInteger() {
	return new RegexProperty(INTEGER) .asInteger() 
}

RegexProperty adouble() {
	return new RegexProperty(DOUBLE) .asdouble() 
}

RegexProperty ipAddress() {
	return new RegexProperty(IP_ADDRESS) .asstring() 
}

RegexProperty hostname() {
	return new RegexProperty(HOSTNAME_PATTERN) .asstring() 
}

RegexProperty email() {
	return new RegexProperty(EMAIL) .asstring() 
}

RegexProperty url() {
	return new RegexProperty(URL) .asstring() 
}

RegexProperty httpsUrl() {
	return new RegexProperty(HTTPS_URL) .asstring() 
}

RegexProperty uuid() {
	return new RegexProperty(UUID) .asstring() 
}

RegexProperty isoDate() {
	return new RegexProperty(ANY_DATE) .asstring() 
}

RegexProperty isoDateTime() {
	return new RegexProperty(ANY_DATE_TIME) .asstring() 
}

RegexProperty isoTime() {
	return new RegexProperty(ANY_TIME) .asstring() 
}

RegexProperty iso8601WithOffset() {
	return new RegexProperty(ISO8601_WITH_OFFSET) .asstring() 
}

RegexProperty nonEmpty() {
	return new RegexProperty(NON_EMPTY) .asstring() 
}

RegexProperty nonBlank() {
	return new RegexProperty(NON_BLANK) .asstring() 
}

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 ('[email protect]')),
				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('[email protect]'))}]"
		) 
	 } 
 }

To make matter even simple you can use a set of predefined object that will automatically assume that you want a regular expression to be pass .
All is start of those method start withany prefix:

T anyAlphaUnicode() 

T anyAlphaNumeric() 

T anyNumber() 

T anyInteger() 

T anyPositiveInt() 

T anydouble() 

T anyHex() 

T aBoolean() 

T anyIpAddress() 

T anyHostname() 

T anyEmail() 

T anyUrl() 

T anyHttpsUrl() 

T anyUuid() 

T anyDate() 

T anyDateTime() 

T anyTime() 

T anyIso8601WithOffset() 

T anyNonBlankstring() 

T anyNonEmptystring() 

T anyOf(string... values)

and this is is is an example of how you can reference those method :

Contract contractDsl = Contract.make {
	label ' trigger_event '
	input {
		triggeredBy('tostring()') 
	}
	outputMessage {
		sentTo 'topic.rateablequote'
		body([
				alpha            :$(anyAlphaUnicode()),
				number           :$(anyNumber()),
				anInteger        :$(anyInteger()),
				positiveInt      :$(anyPositiveInt()),
				adouble          :$(anydouble()),
				aBoolean         :$(aBoolean()),
				ip               :$(anyIpAddress()),
				hostname         :$(anyHostname()),
				email            :$(anyEmail()),
				url              :$(anyUrl()),
				httpsUrl         :$(anyHttpsUrl()),
				uuid             :$(anyUuid()),
				date             :$(anyDate()),
				dateTime         :$(anyDateTime()),
				time             :$(anyTime()),
				iso8601WithOffset:$(anyIso8601WithOffset()),
				nonBlankstring   :$(anyNonBlankstring()),
				nonEmptystring   :$(anyNonEmptystring()),
				anyOf            :$(anyOf(' foo ','bar')) 
		]) 
	 } 
 }

9.5.3 Passing Optional Parameters

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 { 
	 priority1
	request {
		method ' POST '
		url ' /users / password '
		headers {
			contentType(applicationJson()) 
		}
		body(
				email:$(consumer(optional(regex(email())) ) ,producer ('[email protect]')),
				callback_url:$(consumer(regex(hostname()) ) ,producer ('https://partners.com')) 
		) 
	}
	response {
		status 404
		 header { 
			 header' Content  -Type ':' application / json '
		}
		body(
				code:value(consumer(" 123123 "),producer(optional(" 123123 "))) 
		) 
	 } 
 }

By wrapping a part of the body with the optional ( ) method,you create a regular
expression that must be present 0 ormore times.

If you use Spock for,the following test would be generated from the previous example:

					"""
 given:
  def request = given() 
    .header(" Content  -Type "," application / json ") 
    .body('''{"email":"[email protect]","callback_url":"https://partners.com"}''') 

 when:
  def response = given() .spec(request) 
    .post(" /users / password ") 

 then:
  response.statusCode == 404
   response.header (' Content  -Type ')  == ' application / json '
 and:
  DocumentContext parsedJson = JsonPath.parse(response.body.asstring()) 
  assertThatJson(parsedJson) .field("['code']") .matches("(123123)?") 
"""

The following stub would also be generated:

					'''
{
  " request " :{
	" url " :" /users / password ",
	" method " :" POST ",
	" bodypattern " :[ {
	  " 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})?/ ) ] "
	} ],
	" header " :{
	  " Content  -Type " :{
		" equalTo " :" application / json "
	  }
	}
  } ,
  " response " :{
	" status " :404,
	" body " :" { \\ "code\\"  :\\ "123123\\",\\"message\\"  :\\ "User not found by email == [not.existing@user.com]\\"}",
	" header " :{
	  " Content  -Type " :" application / json "
	}
  } ,
  "priority" :1
}
'''

9.5.4 Executing Custom Methods on the Server Side

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 { 
		 method'PUT'
		url $(consumer(regex('^/api/[0-9]{2}$') ) ,producer ('/api/12')) 
		 header { 
			 header' Content  -Type ':' application / json '
		 } 
		 body''' \ 
			 [ { 
				"text":"Gonna see you at Warsaw"
			 } ] 
		'''
	}
	response {
		body(
				path:$(consumer('/api/12'),producer(regex('^/api/[0-9]{2}$'))),
				correlationId:$(consumer(' 1223456 ') ,producer(execute ('isProperCorrelationId($it)'))) 
		) 
		status OK() 
	 } 
 }

The following code shows the base class portion of the test case:

abstract class  BaseMockMvcSpecextends  Specification { 

	 def setup ( ) { 
		 restassuredmockmvc.standalonesetup (new PairIdController()) 
	}

	void isProperCorrelationId(Integer correlationId) {
		assert correlationId == 123456
	}

	void isEmpty(string value) {
		assert value == null
	}

}
important

You cannot use both a string and execute to perform concatenation. For
example,calling header('Authorization','Bearer ' + execute('authToken()')) leads to
improper results. Instead,call header('Authorization',execute('authToken()')) and
ensure that the authToken() method returns everything you need.

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 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 take from
a method .

important

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

The follow example is shows show 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() 
	 } 
 }

The precede example is results result in call thehashCode() method in the request body.
It should resemble the following code:

 MockMvcRequestSpecification request = given() 
   .body(hashCode());


 ResponseOptions response = given() .spec(request) 
   .get(" /something ");


 assertThat(response.statusCode()) .isEqualTo(200);

9.5.5   reference the request from the Response

The best situation is to provide fixed values,but sometimes you need to reference a
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 is use can use thefollowing
options:

  • fromRequest ( ) .url():Returns the request URL and query parameter.
  • 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.

If you is using ’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 parameter.
  • {{{ 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 .  


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:

  MockMvcRequestSpecification request = give ( ) 
    .header ("Authorization"," secret ") 
   .header("Authorization"," secret2 ") 
   .body(" { \"foo\":\"bar\",\"baz\":5 } ");


 ResponseOptions response = given() .spec(request) 
   .queryParam("foo"," bar ") 
   .queryParam("foo","bar2") 
   .get("/api/v1/xxxx");


 assertThat(response.statusCode()) .isEqualTo(200);
 assertThat(response.header("Authorization")) .isEqualTo(" foo secret bar ");

 DocumentContext parsedJson = JsonPath.parse(response.getBody() .asstring());
 assertThatJson(parsedJson) .field(" [ ' fullbody ' ] ") .isEqualTo(" { \"foo\":\"bar\",\"baz\":5 } ");
 assertThatJson(parsedJson) .field(" [ ' authorization ' ] ") .isEqualTo(" secret ");
 assertThatJson(parsedJson) .field("['authorization2']") .isEqualTo(" secret2 ");
 assertThatJson(parsedJson) .field("['path']") .isEqualTo("/api/v1/xxxx");
 assertThatJson(parsedJson) .field(" [ ' param ' ] ") .isEqualTo(" bar ");
 assertThatJson(parsedJson) .field("['paramIndex']") .isEqualTo("bar2");
 assertThatJson(parsedJson) .field("['pathIndex']") .isEqualTo(" v1 ");
 assertThatJson(parsedJson) .field("['responseBaz']") .isEqualTo(5);
 assertThatJson(parsedJson) .field(" [ ' responseFoo ' ] ") .isEqualTo(" bar ");
 assertThatJson(parsedJson) .field("['url']") .isEqualTo("/api/v1/xxxx?foo=bar&foo=bar2");
 assertThatJson(parsedJson) .field(" [ ' responseBaz2 ' ] ") .isEqualTo(" bla bla bar bla bla ");

As you can see,elements from the request have been properly referenced in the response.

The generated WireMock stub should resemble the following example:

{
  " request " :{
    " urlpath " :"/api/v1/xxxx",
    " method " :" POST ",
    " header " :{
      "Authorization" :{
        " equalTo " :" secret2 "
      }
    } ,
    "queryParameters" :{
      "foo" :{
        " equalTo " :"bar2"
      }
    } ,
    " bodypattern " :[ {
      " matchesjsonpath " :"$[?(@.['baz'] == 5)]"
    } , {
      " matchesjsonpath " :"$[?(@.[' foo '] == 'bar')]"
    } ]
  } ,
  " response " :{
    " status " :200,
    " body " :"{\"authorization\":\"{{{request.headers.Authorization.[0]}}}\",\"path\":\"{{{request.path}}}\",\"responseBaz\":{{{jsonpath this '$.baz'}}} ,\"param\":\"{{{request.query.foo.[0]}}}\",\"pathIndex\":\"{{{request.path.[1]}}}\",\"responseBaz2\":\"Bla bla {{{jsonpath this '$ .foo'}}} bla bla\",\"responseFoo\":\"{{{jsonpath this '$ .foo'}}}\",\"authorization2\":\"{{{request.headers.Authorization.[1]}}}\",\"fullBody\":\"{{{escapejsonbody}}}\",\"url\":\"{{{request.url}}}\",\"paramIndex\":\"{{{request.query.foo.[1]}}}\"}",
    " header " :{
      "Authorization" :"{{{request.headers.Authorization.[0]}}};foo"
    } ,
    "transformers" :[ "response-template" ]
  }
}

Sending a request such as the one presented in the request part of the contract results
in sending the following response body:

{
  " url " :"/api/v1/xxxx?foo=bar&foo=bar2",
  "path" :"/api/v1/xxxx",
  "pathIndex" :" v1 ",
  " param " :" bar ",
  "paramIndex" :"bar2",
  "authorization" :" secret ",
  "authorization2" :" secret2 ",
  " fullbody " :" { \"foo\":\"bar\",\"baz\":5 } ",
  "responseFoo" :" bar ",
  " responseBaz " :5,
  "responseBaz2" :" bla bla bar bla bla "
}
important

This feature works only with WireMock having a version greater than orequal
to 2.5.1. The Spring Cloud Contract Verifier uses WireMock’s
response-template response transformer. It uses Handlebars to convert the Mustache { { { } } } templates into
proper values. Additionally,it registers two helper functions:

  • escapejsonbody:Escapes the request body in a format that can be embedded in a JSON.
  • jsonpath:Fora given parameter,find an object in the request body.

9.5.6 Registering Your Own WireMock Extension

WireMock lets you register custom extensions. By default,Spring Cloud Contract registers
the transformer,which lets you reference a request from a response. If you want to
provide your own extensions,you can register an implementation of the
org.springframework.cloud.contract.verifier.dsl.wiremock . WireMockExtensions interface.
Since we use the spring.factories extension approach,you can create an entry in
META-INF/spring.factories file similar to the following:

org.springframework.cloud.contract.verifier.dsl.wiremock . WireMockExtensions=\
org.springframework.cloud.contract.stubrunner.provider.wiremock.TestWireMockExtensions
org.springframework.cloud.contract.spec.ContractConverter=\
org.springframework.cloud.contract.stubrunner.TestCustomYamlContractConverter

The following is an example of a custom extension:

TestWireMockExtensions.groovy. 


package org.springframework.cloud.contract.verifier.dsl.wiremock

import com.github.tomakehurst.wiremock.extension.Extension

/ * * 
  * extension that register the default transformer and the custom one 
  * /
class TestWireMockExtensions implements WireMockExtensions {
	@Override
	list<Extension> extensions() {
		return [
				new DefaultResponseTransformer(),
				new CustomExtension() 
		]
	 } 
 }

class  CustomExtensionimplements  Extension { 

	@Override
	string getName() {
		return " foo  -transformer "
	 } 
 }

important

Remember to override the applyGlobally() method and set it to false if you
want the transformation to be applied only for a mapping that explicitly requires it.

9.5.7 Dynamic Properties in the Matchers Sections

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 is use can use thebodymatcher section for two reasons:

  • define the dynamic value that should end up in a stub .
    You is set can set it in therequest orinputMessage part of your contract.
  • Verify the result of your test.
    This section is present in the response oroutputMessage side of the
    contract.

Currently,Spring Cloud Contract Verifier supports only JSON Path-based matcher with the
following matching possibilities:

YAML .  Please read the Groovy section for detailed explanation of
what the types mean

ForYAML the structure of a matcher looks like this

- path:$ .foo
  type:by_regex
  value:bar
  regextype:as_string

Or if you is want want to use one of the predefined regular expression
[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.

  • ForstubMatchers:

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

      • there are 2 additional fields accepted:minoccurrence and maxoccurrence.
  • FortestMatchers:

    • 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

You is define can also define which type the regular expression correspond to via theregextype field . Below you is find can find the allow list of regular expression type :

  • as_integer
  • as_double
  • as_float,
  • as_long
  • as_short
  • as_boolean
  • as_string

consider the follow 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 '
				]
		]) 
		bodymatcher {
			jsonPath(' $ .duck ',byRegex(" [ 0 - 9]{3 } ") .asInteger()) 
			jsonPath(' $ .duck ',byEquality()) 
			jsonPath(' $ .alpha ',byRegex(onlyAlphaUnicode()) .asstring()) 
			jsonPath(' $ .alpha ',byEquality()) 
			jsonPath('$.number',byRegex(number()) .asInteger()) 
			jsonPath(' $ .aBoolean ',byRegex(anyBoolean()) .asBooleanType()) 
			jsonPath('$.date',byDate()) 
			jsonPath(' $ .dateTime ',byTimestamp()) 
			jsonPath(' $ .time ',byTime()) 
			jsonPath(" \$.['key'].['complex.key ' ] ",byEquality()) 
		}
		headers {
			contentType(applicationJson()) 
		}
	}
	response {
		status OK() 
		body([
				duck                 :123,
				alpha                :' abc ',
				number               :123,
				positiveInteger      :1234567890,
				negativeInteger      :-1234567890,
				positiveDecimalNumber:123.4567890,
				negativeDecimalNumber:-123.4567890,
				aBoolean             :true,
				date                 :'2017-01-01',
				dateTime             :' 2017 - 01 - 01T01:23:45 ',
				time                 :" 01:02:34 ",
				valueWithoutAMatcher :' foo ',
				valueWithTypeMatch   :'string',
				valueWithMin         :[
						1,2,3
				],
				valueWithMax         :[
						1,2,3
				],
				valueWithMinMax      :[
						1,2,3
				],
				valueWithMinEmpty    :[],
				valueWithMaxEmpty    :[],
				key                  :[
						'complex.key':' foo '
				],
				nullValue            :null
		]) 
		bodymatcher {
			
			jsonPath(' $ .duck ',byRegex(" [ 0 - 9]{3 } ") .asInteger()) 
			
			jsonPath(' $ .duck ',byEquality()) 
			
			jsonPath(' $ .alpha ',byRegex(onlyAlphaUnicode()) .asstring()) 
			jsonPath(' $ .alpha ',byEquality()) 
			jsonPath('$.number',byRegex(number()) .asInteger()) 
			jsonPath('$.positiveInteger',byRegex(anInteger()) .asInteger()) 
			jsonPath('$.negativeInteger',byRegex(anInteger()) .asInteger()) 
			jsonPath(' $ .positiveDecimalNumber ',byRegex(adouble()) .asdouble()) 
			jsonPath('$.negativeDecimalNumber',byRegex(adouble()) .asdouble()) 
			jsonPath(' $ .aBoolean ',byRegex(anyBoolean()) .asBooleanType()) 
			
			jsonPath('$.date',byDate()) 
			jsonPath(' $ .dateTime ',byTimestamp()) 
			jsonPath(' $ .time ',byTime()) 
			
			jsonPath('$.valueWithTypeMatch',byType()) 
			jsonPath('$.valueWithMin',byType {
				
				minoccurrence(1) 
			}) 
			jsonPath('$.valueWithMax',byType {
				
				maxoccurrence(3) 
			}) 
			jsonPath('$.valueWithMinMax',byType {
				
				minoccurrence(1) 
				maxoccurrence(3) 
			}) 
			jsonPath('$.valueWithMinEmpty',byType {
				
				minoccurrence(0) 
			}) 
			jsonPath('$.valueWithMaxEmpty',byType {
				
				maxoccurrence(0) 
			}) 
			
			jsonPath(' $ .duck ',bycommand('assertThatValueIsANumber($it)')) 
			jsonPath(" \$.['key'].['complex.key ' ] ",byEquality()) 
			jsonPath(' $ .nullValue ',byNull()) 
		}
		headers {
			contentType(applicationJson()) 
			header('Some-Header',$(c('someValue'),p(regex('[a-zA-Z]{9}')))) 
		}
	 } 
 }

YAML .  

request:
  method:GET
  urlPath:/get/1
  headers:
    Content-Type:application/json
  cookies:
    foo:2
    bar:3
  queryParameters:
    limit:10
    offset:20
    filter:'email'
    sort:name
    search:55
    age:99
    name:John.Doe
    email:'[email protect]'
  body:
    duck:123
    alpha:"abc"
    number:123
    aBoolean:true
    date:"2017-01-01"
    dateTime:"2017-01-01T01:23:45"
    time:" 01:02:34 "
    valueWithoutAMatcher:"foo"
    valueWithTypeMatch:"string"
    key:
      "complex.key":' foo '
    nullValue:null
    valueWithMin:
       -1
       -2
       -3
    valueWithMax:
       -1
       -2
       -3
    valueWithMinMax:
       -1
       -2
       -3
    valueWithMinEmpty:[]
    valueWithMaxEmpty:[]
  matcher:
    url:
      regex:/get/[0-9]
      # predefined:
      # execute a method
      #command:'equals($it)'
    queryParameters:
       -key:limit
        type:equal_to
        value:20
       -key:offset
        type:containing
        value:20
       -key:sort
        type:equal_to
        value:name
       -key:search
        type:not_matching
        value:'^[0-9]{2}$'
       -key:age
        type:not_matching
        value:'^\\w*$'
       -key:name
        type:matching
        value:'John.*'
       -key:hello
        type:absent
    cookies:
       -key:foo
        regex:'[0-9]'
       -key:bar
        command:'equals($it)'
    headers:
       -key:Content-Type
        regex:"application/json.*"
    body:
       -path:$.duck
        type:by_regex
        value:" [ 0 - 9]{3 } "
       -path:$.duck
        type:by_equality
       -path:$.alpha
        type:by_regex
        predefined:only_alpha_unicode
       -path:$.alpha
        type:by_equality
       -path:$.number
        type:by_regex
        predefined:number
       -path:$.aBoolean
        type:by_regex
        predefined:any_boolean
       -path:$.date
        type:by_date
       -path:$.dateTime
        type:by_timestamp
       -path:$.time
        type:by_time
       -path:"$.['key'].['complex.key']"
        type:by_equality
       -path:$.nullvalue
        type:by_null
       -path:$.valueWithMin
        type:by_type
        minoccurrence:1
       -path:$.valueWithMax
        type:by_type
        maxoccurrence:3
       -path:$.valueWithMinMax
        type:by_type
        minoccurrence:1
        maxoccurrence:3
response:
  status:200
  cookies:
    foo:1
    bar:2
  body:
    duck:123
    alpha:"abc"
    number:123
    aBoolean:true
    date:"2017-01-01"
    dateTime:"2017-01-01T01:23:45"
    time:" 01:02:34 "
    valueWithoutAMatcher:"foo"
    valueWithTypeMatch:"string"
    valueWithMin:
       -1
       -2
       -3
    valueWithMax:
       -1
       -2
       -3
    valueWithMinMax:
       -1
       -2
       -3
    valueWithMinEmpty:[]
    valueWithMaxEmpty:[]
    key:
      'complex.key':' foo '
    nulValue:null
  matcher:
    headers:
       -key:Content-Type
        regex:"application/json.*"
    cookies:
       -key:foo
        regex:'[0-9]'
       -key:bar
        command:'equals($it)'
    body:
       -path:$.duck
        type:by_regex
        value:" [ 0 - 9]{3 } "
       -path:$.duck
        type:by_equality
       -path:$.alpha
        type:by_regex
        predefined:only_alpha_unicode
       -path:$.alpha
        type:by_equality
       -path:$.number
        type:by_regex
        predefined:number
       -path:$.aBoolean
        type:by_regex
        predefined:any_boolean
       -path:$.date
        type:by_date
       -path:$.dateTime
        type:by_timestamp
       -path:$.time
        type:by_time
       -path:$.valueWithTypeMatch
        type:by_type
       -path:$.valueWithMin
        type:by_type
        minoccurrence:1
       -path:$.valueWithMax
        type:by_type
        maxoccurrence:3
       -path:$.valueWithMinMax
        type:by_type
        minoccurrence:1
        maxoccurrence:3
       -path:$.valueWithMinEmpty
        type:by_type
        minoccurrence:0
       -path:$.valueWithMaxEmpty
        type:by_type
        maxoccurrence:0
       -path:$.duck
        type:by_command
        value:assertThatValueIsANumber($it) 
       -path:$.nullValue
        type:by_null
        value:null
  headers:
    Content-Type:application/json

In the preceding example,you can see the dynamic portions of the contract in the
matcher sections. Forthe 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. Forthe valueWithoutAMatcher,the verification takes place
in the same way as without the use of matcher. In that case,the test performs an
equality check.

Forthe response side in thebodymatcher section,we define the dynamic parts in a
similar manner. The only difference is that the byType matcher are also present. The
verifier engine checks four fields to verify whether the response from the test
has a value for which the JSON path matches the given field,is of the same type as the one
defined in the response body,and passes the following check (based on the method being called):

  • For$.valueWithTypeMatch,the engine checks whether the type is the same.
  • For$.valueWithMin,the engine check the type and asserts whether the size is greater
    than orequal to the minimum occurrence.
  • For$.valueWithMax,the engine checks the type and asserts whether the size is
    smaller than orequal to the maximum occurrence.
  • For$.valueWithMinMax,the engine checks the type and asserts whether the size is
    between the min and maximum occurrence.

The result test would resemble the following example ( note that is note anand section
separates the autogenerated assertions and the assertion from matcher):

  MockMvcRequestSpecification request = give ( ) 
    .header (" Content  -Type "," application / json ") 
   .body("{\"duck\":123,\"alpha\":\"abc\",\"number\":123,\"aBoolean\":true,\"date\":\"2017-01-01\",\"dateTime\":\"2017-01-01T01:23:45\",\"time\":\"01:02:34\",\"valueWithoutAMatcher\":\"foo\",\"valueWithTypeMatch\":\"string\",\"key\":{\"complex.key\":\"foo\"}}");


 ResponseOptions response = given() .spec(request) 
   .get("/get");


 assertThat(response.statusCode()) .isEqualTo(200);
 assertThat(response.header(" Content  -Type ")) .matches("application/json.*");

 DocumentContext parsedJson = JsonPath.parse(response.getBody() .asstring());
 assertThatJson(parsedJson) .field("['valueWithoutAMatcher']") .isEqualTo("foo");

 assertThat(parsedJson.read("$.duck",string.class)) .matches(" [ 0 - 9]{3 } ") ; 
  assertThat(parsedJson.read ("$.duck",Integer.class)) .isEqualTo(123) ; 
  assertThat(parsedJson.read (" $ .alpha ",string.class)) .matches("[\\p{L}]*") ; 
  assertThat(parsedJson.read (" $ .alpha ",string.class)) .isEqualTo("abc") ; 
  assertThat(parsedJson.read ("$.number",string.class)) .matches("-?(\\d*\\.\\d+|\\d+)") ; 
  assertThat(parsedJson.read ("$.aBoolean",string.class)) .matches("(true|false)") ; 
  assertThat(parsedJson.read (" $ .date ",string.class)) .matches("(\\d\\d\\d\\d)-(0[1-9]|1[012])-(0[1-9]|[12] [0-9]|3[01])") ; 
  assertThat(parsedJson.read ("$.dateTime",string.class)) .matches("([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12] [0-9])T(2[0-3]|[01] [0-9] ) :( [0-5] [0-9] ) :( [0-5] [0-9])") ; 
  assertThat(parsedJson.read ("$.time",string.class)) .matches("(2[0-3]|[01] [0-9] ) :( [0-5] [0-9] ) :( [0-5] [0-9])") ; 
  assertThat((Object ) parsedJson.read ("$.valueWithTypeMatch")) .isInstanceOf(java.lang.string.class) ; 
  assertThat((Object ) parsedJson.read (" $ .valueWithMin ")) .isInstanceOf(java.util.list.class) ; 
  assertThat((java.lang . Iterable ) parsedJson.read (" $ .valueWithMin ",java.util.Collection.class)) .as(" $ .valueWithMin ") .hasSizeGreaterThanOrEqualTo(1) ; 
  assertThat((Object ) parsedJson.read (" $ .valueWithMax ")) .isInstanceOf(java.util.list.class) ; 
  assertThat((java.lang . Iterable ) parsedJson.read (" $ .valueWithMax ",java.util.Collection.class)) .as(" $ .valueWithMax ") .hasSizeLessThanOrEqualTo(3) ; 
  assertThat((Object ) parsedJson.read (" $ .valueWithMinMax ")) .isInstanceOf(java.util.list.class) ; 
  assertThat((java.lang . Iterable ) parsedJson.read (" $ .valueWithMinMax ",java.util.Collection.class)) .as(" $ .valueWithMinMax ") .hasSizeBetween(1,3) ; 
  assertThat((Object ) parsedJson.read (" $ .valuewithminempty ")) .isInstanceOf(java.util.list.class) ; 
  assertThat((java.lang . Iterable ) parsedJson.read (" $ .valuewithminempty ",java.util.Collection.class)) .as(" $ .valuewithminempty ") .hasSizeGreaterThanOrEqualTo(0) ; 
  assertThat((Object ) parsedJson.read (" $ .valuewithmaxempty ")) .isInstanceOf(java.util.list.class) ; 
  assertThat((java.lang . Iterable ) parsedJson.read (" $ .valuewithmaxempty ",java.util.Collection.class)) .as(" $ .valuewithmaxempty ") .hasSizeLessThanOrEqualTo(0);
 assertThatValueIsANumber(parsedJson.read("$.duck")) ; 
  assertThat(parsedJson.read ("$.['key'].['complex.key']",string.class)) .isEqualTo("foo");
important

Notice that,for the bycommand method,the example calls the
assertThatValueIsANumber. This method must be defined in the test base class orbe
statically imported to your tests. Notice that the bycommand call was convert to
assertThatValueIsANumber(parsedJson.read("$.duck"));. That is means mean that the engine take
the method name and pass the proper JSON path as a parameter to it .

The result WireMock stub is is is in the following example :

					'''
{
  " request " :{
    " urlpath " :"/get",
    " method " :" POST ",
    " header " :{
      " Content  -Type " :{
        " match " :"application/json.*"
      }
    } ,
    " bodypattern " :[ {
      " matchesjsonpath " :"$.['list'].['some'].['nested'] [?(@.['anothervalue'] == 4)]"
    } , {
      " matchesjsonpath " :"$[?(@.['valueWithoutAMatcher'] == ' foo ')]"
    } , {
      " matchesjsonpath " :"$[?(@.['valueWithTypeMatch'] == 'string')]"
    } , {
      " matchesjsonpath " :"$.['list'].['someother'].['nested'] [?(@.['json'] == 'with value')]"
    } , {
      " matchesjsonpath " :"$.['list'].['someother'].['nested'] [?(@.['anothervalue'] == 4)]"
    } , {
      " matchesjsonpath " :" $ [ ? ( @.duck = ~ /([0 - 9]{3})/ ) ] "
    } , {
      " matchesjsonpath " :"$[?(@.duck == 123)]"
    } , {
      " matchesjsonpath " :"$[?(@.alpha =~ /([\\\\p{L}]*)/)]"
    } , {
      " matchesjsonpath " :"$[?(@.alpha == ' abc ')]"
    } , {
      " matchesjsonpath " :"$[?(@.number =~ /(-?(\\\\d*\\\\.\\\\d+|\\\\d+))/)]"
    } , {
      " matchesjsonpath " :"$[?(@.aBoolean =~ /((true|false))/)]"
    } , {
      " matchesjsonpath " :"$[?(@.date =~ /((\\\\d\\\\d\\\\d\\\\d)-(0[1-9]|1[012])-(0[1-9]|[12] [0-9]|3[01]))/)]"
    } , {
      " matchesjsonpath " :"$[?(@.dateTime =~ /(([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12] [0-9])T(2[0-3]|[01] [0-9] ) :( [0-5] [0-9] ) :( [0-5] [0-9]))/)]"
    } , {
      " matchesjsonpath " :"$[?(@.time =~ /((2[0-3]|[01] [0-9] ) :( [0-5] [0-9] ) :( [0-5] [0-9]))/)]"
    } , {
      " matchesjsonpath " :"$.list.some.nested[?(@.json =~ /(.*)/)]"
    } , {
      " matchesjsonpath " :"$[?(@.valueWithMin.size() >= 1)]"
    } , {
      " matchesjsonpath " :"$[?(@.valueWithMax.size() <= 3)]"
    } , {
      " matchesjsonpath " :"$[?(@.valueWithMinMax.size() >= 1 && @.valueWithMinMax.size() <= 3)]"
    } , {
      " matchesjsonpath " :"$[?(@.valueWithOccurrence.size() >= 4 && @.valueWithOccurrence.size() <= 4)]"
    } ]
  } ,
  " response " :{
    " status " :200,
    " body " :" { \\ "duck\\"  :123,\\ "alpha\\"  :\\ "abc\\",\\"number\\"  :123,\\ "aBoolean\\":true,\\"date\\"  :\\ "2017-01-01\\",\\"dateTime\\"  :\\ "2017-01-01T01:23:45\\",\\"time\\"  :\\ "01:02:34\\",\\"valueWithoutAMatcher\\"  :\\ "foo\\",\\"valueWithTypeMatch\\"  :\\ "string\\",\\"valueWithMin\\":[1,2,3],\\"valueWithMax\\":[1,2,3],\\"valueWithMinMax\\":[1,2,3],\\"valueWithOccurrence\\"  :[ 1,2,3,4 ] } ",
    " header " :{
      " Content  -Type " :" application / json "
    } ,
    "transformers" :[ "response-template" ]
  }
}
'''
important

If you use a matcher,then the part of the request and response that the
matcher addresses with the JSON Path gets removed from the assertion. In the case of
verifying a collection,you must create matcher for all the element of the
collection .

consider the follow example :

contract.make { 
     request { 
         method' GET '
         url (" /foo ") 
    }
    response {
        status OK() 
        body(events:[[
                                 operation          :' EXPORT ',
                                 eventId            :' 16f1ed75 - 0bcc-4f0d - a04d-3121798faf99 ',
                                 status             :'OK'
                         ],[
                                 operation          :'INPUT_PROCESSING',
                                 eventId            :' 3bb4ac82 - 6652 - 462f - b6d1 - 75e424a0024a ',
                                 status             :'OK'
                         ]
                ]
        ) 
        bodymatcher {
            jsonPath('$.events[0].operation',byRegex(' .+ ')) 
            jsonPath(' $ .events[0].eventId ',byRegex(' ^([a - fa - F0 - 9]{8}-[a - fa - F0 - 9]{4}-[a - fa - F0 - 9]{4}-[a - fa - F0 - 9]{4}-[a - fa - F0 - 9]{12})$ ')) 
            jsonPath('$.events[0].status',byRegex(' .+ ')) 
        }
    }
}

The precede code lead to create the follow test ( the code block is shows show only the assertion section ):

and:
	DocumentContext parsedJson = JsonPath.parse(response.body.asstring()) 
	assertThatJson(parsedJson) .array(" [ ' event ' ] ") .contains("['eventId']") .isEqualTo(" 16f1ed75 - 0bcc-4f0d - a04d-3121798faf99 ") 
	assertThatJson(parsedJson) .array(" [ ' event ' ] ") .contains("['operation']") .isEqualTo("EXPORT") 
	assertThatJson(parsedJson) .array(" [ ' event ' ] ") .contains("['operation']") .isEqualTo(" input_processing ") 
	assertThatJson(parsedJson) .array(" [ ' event ' ] ") .contains("['eventId']") .isEqualTo("3bb4ac82-6652-462f-b6d1-75e424a0024a") 
	assertThatJson(parsedJson) .array(" [ ' event ' ] ") .contains(" [ ' status ' ] ") .isEqualTo("OK") 
and:
	assertThat(parsedJson.read("\$.events[0].operation",string.class)) .matches(".+") 
	assertThat(parsedJson.read("\$.events[0].eventId",string.class)) .matches(" ^([a - fa - F0 - 9]{8}-[a - fa - F0 - 9]{4}-[a - fa - F0 - 9]{4}-[a - fa - F0 - 9]{4}-[a - fa - F0 - 9]{12})\$ ") 
	assertThat(parsedJson.read("\$.events[0].status",string.class)) .matches(".+")

As you can see,the assertion is malformed. Only the first element of the array got
asserted. In order to fix this,you should apply the assertion to the whole $.events
collection and assert it with the bycommand(…​) method .