diff --git a/spring-cloud-contract.html b/spring-cloud-contract.html index 44fe7a9c7b..fa0b662cf2 100644 --- a/spring-cloud-contract.html +++ b/spring-cloud-contract.html @@ -452,30 +452,17 @@ body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-b
  • Samples
  • -
  • Why use Spring Cloud Contract Verifier and not X ?
  • -
  • Contract DSL +
  • FAQ @@ -654,6 +641,31 @@ body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-b
  • +
  • Contract DSL + +
  • Links
  • @@ -881,6 +893,21 @@ tested against the producer’s side. In other words - you can trust those s +
    + + + + + +
    +
    Important
    +
    +Spring Cloud Contract Verifier’s purpose is NOT to start writing business features in the contracts. +Let’s assume that we have a business use case of fraud check. If a user can be a fraud for 100 different reasons, +we would assume that you would create 2 contracts. One for the positive and one for the negative fraud case. +Contract tests are used to test contracts between applications and not to simulate full behaviour. +
    +

    Client Side

    @@ -1153,6 +1180,19 @@ either by means of a map notation or String with interpolations. Consult the docs for more information. We highly recommend using the map notation!

    +
    + + + + + +
    +
    Tip
    +
    +It’s really important that you understand the map notation to set up contracts. Please read the +Groovy docs regarding JSON +
    +

    The aforementioned contract is an agreement between two sides that:

    @@ -1635,6 +1675,9 @@ is under constant development.

  • Spring Cloud Contract related articles from Marcin Grzejszczak’s blog

  • +
  • +

    Groovy docs regarding JSON

    +
  • @@ -1645,6 +1688,9 @@ is under constant development.

    Here you can find some samples.

    + +
    +

    FAQ

    Why use Spring Cloud Contract Verifier and not X ?

    @@ -1675,341 +1721,103 @@ Spring Cloud Contract Verifier stand out on the "market" of Consumer Driven Cont
    -
    -
    -

    Contract DSL

    -
    - - - - - -
    -
    Important
    -
    -Remember that inside the contract file you have to provide the fully qualified name to -the Contract class and the make static import i.e. 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 { …​ } -
    -
    -
    -

    Contract DSL is written in Groovy, but don’t be alarmed if you didn’t use Groovy before. Knowledge of the language is not really needed as our DSL uses only -a tiny subset of it (namely literals, method calls and closures). What’s more the DSL is designed to be programmer-readable without any knowledge of the DSL itself - - it’s statically typed.

    -
    -
    -

    The Contract is present in the spring-cloud-contract-spec module of the Spring Cloud Contract Verifier repository.

    -
    -
    -

    Let’s look at full example of a 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 200
    -	}
    -}
    -
    -
    -
    -

    Not all features of the DSL are used in example above. If you didn’t find what you are looking for, please check next paragraphs on this page.

    -
    -
    -
    -
    -

    You can easily compile Contracts to WireMock stubs mapping using standalone maven command: mvn org.springframework.cloud:spring-cloud-contract-maven-plugin:convert.

    -
    -
    -
    -

    Limitations

    -
    - - - - - -
    -
    Warning
    -
    -Spring Cloud Contract Verifier doesn’t support XML properly. Please use JSON or help us implement this feature. -
    -
    -
    - - - - - -
    -
    Warning
    -
    -Spring Cloud Contract Verifier supports equality check on text response. Regular expressions are not yet available. -
    -
    -
    - - - - - -
    -
    Warning
    -
    -The support for the verification of size of JSON arrays is experimental. If you want to turn it on please provide -the value of a system property spring.cloud.contract.verifier.assert.size equal to true. By default this feature is set to -false. You can also provide the assertJsonSize property in the plugin configuration. -
    -
    -
    - - - - - -
    -
    Warning
    -
    -Due to the fact that JSON structure can have any form it’s sometimes impossible to parse it properly when using -the value(consumer(…​), producer(…​)) notation when using that in GString. That’s why we highly recommend using the -Groovy Map notation. -
    -
    -
    -
    -

    HTTP Top-Level Elements

    +

    What is this value(consumer(), producer()) ?

    -

    Following methods can be called in the top-level closure of a contract definition. Request and response are mandatory, priority is optional.

    +

    One of the biggest challenges related to stubs is their reusability. Only if they can be vastly used, will they serve their purpose. +What typically makes that difficult are the hard-coded values of request / response elements. For example dates or ids. +Imagine the following JSON request

    -
    org.springframework.cloud.contract.spec.Contract.make {
    -	// Definition of HTTP request part of the contract
    -	// (this can be a valid request or invalid depending
    -	// on type of contract being specified).
    -	request {
    -		//...
    -	}
    -
    -	// Definition of HTTP response part of the contract
    -	// (a service implementing this contract should respond
    -	// with following response after receiving request
    -	// specified in "request" part above).
    -	response {
    -		//...
    -	}
    -
    -	// Contract priority, which can be used for overriding
    -	// contracts (1 is highest). Priority is optional.
    -	priority 1
    -}
    -
    -
    -
    -
    -

    Request

    -
    -

    HTTP protocol requires only method and address to be specified in a request. The same information is mandatory in request definition of the Contract.

    -
    -
    -
    -
    org.springframework.cloud.contract.spec.Contract.make {
    -	request {
    -		// HTTP request method (GET/POST/PUT/DELETE).
    -		method 'GET'
    -
    -		// Path component of request URL is specified as follows.
    -		urlPath('/users')
    -	}
    -
    -	response {
    -		//...
    -	}
    +
    {
    +    "time" : "2016-10-10 20:10:15",
    +    "id" : "9febab1c-6f36-4a0b-88d6-3b6a6d81cd4a",
    +    "body" : "foo"
     }
    -

    It is possible to specify whole url instead of just path, but urlPath is the recommended way as it makes the tests host-independent.

    +

    and JSON response

    -
    org.springframework.cloud.contract.spec.Contract.make {
    -	request {
    -		method 'GET'
    -
    -		// Specifying `url` and `urlPath` in one contract is illegal.
    -		url('http://localhost:8888/users')
    -	}
    -
    -	response {
    -		//...
    -	}
    +
    {
    +    "time" : "2016-10-10 21:10:15",
    +    "id" : "c4231e1f-3ca9-48d3-b7e7-567d55f0d051",
    +    "body" : "bar"
     }
    -

    Request may contain query parameters, which are specified in a closure nested in a call to urlPath or url.

    +

    Imagine the pain required to set proper value of the time field (let’s assume that this content is generated by the +database) by changing the clock in the system or providing stub implementations of data providers. The same is related +to the field called id. Will you create a stubbed implementation of UUID generator? Makes little sense…​

    +
    +
    +

    So as a consumer you would like to send a request that matches any form of a time or any UUID. That way your system +will work as usual - will generate data and you won’t have to stub anything out. Let’s assume that in case of the aforementioned +JSON the most important part is the body field. You can focus on that and provide matching for other fields. In other words +you would like the stub to work like this:

    -
    org.springframework.cloud.contract.spec.Contract.make {
    -	request {
    -		//...
    -
    -		urlPath('/users') {
    -
    -			// Each parameter is specified in form
    -			// `'paramName' : paramValue` where parameter value
    -			// may be a simple literal or one of matcher functions,
    -			// all of which are used in this example.
    -			queryParameters {
    -
    -				// If a simple literal is used as value
    -				// default matcher function is used (equalTo)
    -				parameter 'limit': 100
    -
    -				// `equalTo` function simply compares passed value
    -				// using identity operator (==).
    -				parameter 'filter': equalTo("email")
    -
    -				// `containing` function matches strings
    -				// that contains passed substring.
    -				parameter 'gender': value(consumer(containing("[mf]")), producer('mf'))
    -
    -				// `matching` function tests parameter
    -				// against passed regular expression.
    -				parameter 'offset': value(consumer(matching("[0-9]+")), producer(123))
    -
    -				// `notMatching` functions tests if parameter
    -				// does not match passed regular expression.
    -				parameter 'loginStartsWith': value(consumer(notMatching(".{0,2}")), producer(3))
    -			}
    -		}
    -
    -		//...
    -	}
    -
    -	response {
    -		//...
    -	}
    +
    {
    +    "time" : "SOMETHING THAT MATCHES TIME",
    +    "id" : "SOMETHING THAT MATCHES UUID",
    +    "body" : "foo"
     }
    -

    It may contain additional request headers…​

    +

    As far as the response goes as a consumer you need a concrete value that you can operate on. So such a JSON is valid

    -
    org.springframework.cloud.contract.spec.Contract.make {
    -	request {
    -		//...
    -
    -		// Each header is added in form `'Header-Name' : 'Header-Value'`.
    -		headers {
    -			header 'Content-Type': 'application/json'
    -		}
    -
    -		//...
    -	}
    -
    -	response {
    -		//...
    -	}
    +
    {
    +    "time" : "2016-10-10 21:10:15",
    +    "id" : "c4231e1f-3ca9-48d3-b7e7-567d55f0d051",
    +    "body" : "bar"
     }
    -

    …​and a request body.

    +

    As you could see in the previous sections we generate tests from contracts. So from the producer’s side the situation looks +much different. We’re parsing the provided contract and in the test we want to send a real request to your endpoints. +So for the case of a producer for the request we can’t have any sort of matching. We need concrete values that the +producer’s backend can work on. Such a JSON would be a valid one:

    -
    org.springframework.cloud.contract.spec.Contract.make {
    -	request {
    -		//...
    -
    -		// Currently only JSON format of request body is supported.
    -		// Format will be determined from a header or body's content.
    -		body '''{ "login" : "john", "name": "John The Contract" }'''
    -	}
    -
    -	response {
    -		//...
    -	}
    -}
    -
    -
    -
    -
    -

    Response

    -
    -

    Minimal response must contain HTTP status code.

    -
    -
    -
    -
    org.springframework.cloud.contract.spec.Contract.make {
    -	request {
    -		//...
    -	}
    -	response {
    -		// Status code sent by the server
    -		// in response to request specified above.
    -		status 200
    -	}
    +
    {
    +    "time" : "2016-10-10 20:10:15",
    +    "id" : "9febab1c-6f36-4a0b-88d6-3b6a6d81cd4a",
    +    "body" : "foo"
     }
    -

    Besides status response may contain headers and body, which are specified the same way as in the request (see previous paragraph).

    +

    On the other hand from the point of view of the validity of the contract the response doesn’t necessarily have to +contain concrete values of time or id. Let’s say that you generate those on the producer side - again, you’d +have to do a lot of stubbing to ensure that you always return the same values. That’s why from the producer’s side +what you might want is the following response:

    +
    +
    +
    {
    +    "time" : "SOMETHING THAT MATCHES TIME",
    +    "id" : "SOMETHING THAT MATCHES UUID",
    +    "body" : "bar"
    +}
    -
    -

    Dynamic properties

    -
    -

    The contract can contain some dynamic properties - timestamps / ids etc. You don’t want to enforce the consumers to stub their -clocks to always return the same value of time so that it gets matched by the stub. That’s why we allow you to provide the dynamic -parts in your contracts in the following way

    -

    either via the value method

    +

    How can you then provide one time a matcher for the consumer and a concrete value for the producer and vice versa? +In Spring Cloud Contract we’re allowing you to provide a dynamic value. That means that it can differ for both +sides of the communication. You can pass the values:

    +
    +
    +

    Either via the value method

    @@ -2019,7 +1827,7 @@ value(client(...), server(...))
    -

    or if you’re using the Groovy map notation for body you can use the $() method

    +

    or using the $() method

    @@ -2029,479 +1837,164 @@ $(client(...), server(...))
    -

    All of the aforementioned approaches are equal. That means that stub and client methods are aliases over the consumer -method. Let’s take a closer look at what we can do with those values in the subsequent sections.

    -
    -
    -
    -

    Regular expressions

    -
    -

    You can use regular expressions to write your requests in Contract DSL. It is particularly useful when you want to indicate that a given response -should be provided for requests that follow a given pattern. Also, you can use it when you need to use patterns and not exact values both -for your test and your server side tests.

    +

    You can read more about this in the Contract DSL section.

    -

    Please see the example below:

    +

    Calling value() or $() tells Spring Cloud Contract that you will be passing a dynamic value. +Inside the consumer() method you pass the value that should be used on the consumer side (in the generated stub). +Inside the producer() method you pass the value that should be used on the producer side (in the generated test).

    +
    +
    + + + + + +
    +
    Tip
    +
    +If on one side you have passed the regular expression and you haven’t passed the other, then the +other side will get auto-generated. +
    +
    +
    +

    Most often you will use that method together with the regex helper method. E.g. consumer(regex('[0-9]{10}')).

    +
    +
    +

    To sum it up the contract for the aforementioned scenario would look more or less like this (the regular expression +for time and UUID are simplified and most likely invalid but we want to keep things very simple in this example):

    org.springframework.cloud.contract.spec.Contract.make {
    -	request {
    -		method('GET')
    -		url $(consumer(~/\/[0-9]{2}/), producer('/12'))
    -	}
    -	response {
    -		status 200
    -		body(
    -				id: value(
    -						consumer('123'),
    -						producer(regex('[0-9]+'))
    -				),
    -				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}'))
    -				)
    -		)
    -		headers {
    -			header 'Content-Type': 'text/plain'
    -		}
    -	}
    +				request {
    +					method 'GET'
    +					url '/someUrl'
    +					body([
    +					    time : value(consumer(regex('[0-9]{4}-[0-9]{2}-[0-9]{2} [0-2][0-9]-[0-5][0-9]-[0-5][0-9]')),
    +					    id: value(consumer(regex('[0-9a-zA-z]{8}-[0-9a-zA-z]{4}-[0-9a-zA-z]{4}-[0-9a-zA-z]{12}'))
    +					    body: "foo"
    +					])
    +				}
    +			response {
    +				status 200
    +				body([
    +					    time : value(producer(regex('[0-9]{4}-[0-9]{2}-[0-9]{2} [0-2][0-9]-[0-5][0-9]-[0-5][0-9]')),
    +					    id: value([producer(regex('[0-9a-zA-z]{8}-[0-9a-zA-z]{4}-[0-9a-zA-z]{4}-[0-9a-zA-z]{12}'))
    +					    body: "bar"
    +					])
    +			}
     }
    -
    -

    You can also provide only one side of the communication using a regular expression. If you do that then automatically we’ll -provide the generated string that matches the provided regular expression. For example:

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

    In this example for request and response the opposite side of the communication will have the respective data generated.

    -
    -
    -

    Spring Cloud Contract comes with a series of predefined regular expressions that you can use in your contracts.

    -
    -
    -
    -
    private static final Pattern TRUE_OR_FALSE = Pattern.compile(/(true|false)/)
    -private static final Pattern ONLY_ALPHA_UNICODE = Pattern.compile(/[\p{L}]*/)
    -private static final Pattern NUMBER = Pattern.compile('-?\\d*(\\.\\d+)?')
    -private static final Pattern IP_ADDRESS = 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])');
    -private static final Pattern HOSTNAME_PATTERN = Pattern.compile('((http[s]?|ftp):\\/)\\/?([^:\\/\\s]+)(:[0-9]{1,5})?');
    -private static final Pattern EMAIL = Pattern.compile('[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,4}');
    -private static final Pattern URL = Pattern.compile('((www\\.|(http|https|ftp|news|file)+\\:\\/\\/)[_.a-z0-9-]+\\.[a-z0-9\\/_:@=.+?,##%&~-]*[^.|\\\'|\\# |!|\\(|?|,| |>|<|;|\\)])')
    -
    -String onlyAlphaUnicode() {
    -	return ONLY_ALPHA_UNICODE.pattern()
    -}
    -
    -String number() {
    -	return NUMBER.pattern()
    -}
    -
    -String anyBoolean() {
    -	return TRUE_OR_FALSE.pattern()
    -}
    -
    -String ipAddress() {
    -	return IP_ADDRESS.pattern()
    -}
    -
    -String hostname() {
    -	return HOSTNAME_PATTERN.pattern()
    -}
    -
    -String email() {
    -	return EMAIL.pattern()
    -}
    -
    -String url() {
    -	return URL.pattern()
    -}
    -
    -
    -
    -

    so in your contract you can use it like this

    -
    -
    -
    -
    Contract dslWithOptionalsInString = Contract.make {
    -	priority 1
    -	request {
    -		method 'POST'
    -		url '/users/password'
    -		headers {
    -			header 'Content-Type': 'application/json'
    -		}
    -		body(
    -				email: $(consumer(optional(regex(email()))), producer('abc@abc.com')),
    -				callback_url: $(consumer(regex(hostname())), producer('http://partners.com'))
    -		)
    -	}
    -	response {
    -		status 404
    -		headers {
    -			header 'Content-Type': 'application/json'
    -		}
    -		body(
    -				code: value(consumer("123123"), producer(optional("123123"))),
    -				message: "User not found by email = [${value(producer(regex(email())), consumer('not.existing@user.com'))}]"
    -		)
    -	}
    -}
    -
    +
    + + + + + +
    +
    Important
    +
    +Please read the Groovy docs related to JSON to understand how to +properly structure the request / response bodies. +
    -

    Passing optional parameters

    +

    How to do Stubs versioning?

    +
    +
    API Versioning
    -

    It is possible to provide optional parameters in your contract. It’s only possible to have optional parameter for the:

    +

    Let’s try to answer a question what versioning really means. If you’re referring to the API version then there are +different approaches.

    • -

      STUB side of the Request

      +

      use Hypermedia, links and do not version your API by any means

    • -

      TEST side of the Response

      +

      pass versions through headers / urls

    -

    Example:

    -
    -
    -
    -
    org.springframework.cloud.contract.spec.Contract.make {
    -	priority 1
    -	request {
    -		method 'POST'
    -		url '/users/password'
    -		headers {
    -			header 'Content-Type': 'application/json'
    -		}
    -		body(
    -				email: $(consumer(optional(regex(email()))), producer('abc@abc.com')),
    -				callback_url: $(consumer(regex(hostname())), producer('http://partners.com'))
    -		)
    -	}
    -	response {
    -		status 404
    -		headers {
    -			header 'Content-Type': 'application/json'
    -		}
    -		body(
    -				code: value(consumer("123123"), producer(optional("123123")))
    -		)
    -	}
    -}
    -
    +

    I will not try to answer a question which approach is better. Whatever suit your needs and allows you to generate +business value should be picked.

    -

    By wrapping a part of the body with the optional() method you are in fact creating a regular expression that should be present 0 or more times.

    +

    Let’s assume that you do version your API. In that case you should provide as many contracts as many versions you support. +You can create a subfolder for every version or append it to th contract name - whatever suits you more.

    -
    -

    That way for the example above the following test would be generated if you pick Spock:

    -
    -
    -
    -
    """
    - given:
    -  def request = given()
    -    .header('Content-Type', 'application/json')
    -    .body('''{"email":"abc@abc.com","callback_url":"http://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)?")
    -"""
    -
    -
    -
    -

    and the following stub:

    -
    -
    -
    -
    '''
    -{
    -  "request" : {
    -    "url" : "/users/password",
    -    "method" : "POST",
    -    "bodyPatterns" : [ {
    -      "matchesJsonPath" : "$[?(@.email =~ /([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,4})?/)]"
    -    }, {
    -      "matchesJsonPath" : "$[?(@.callback_url =~ /((http[s]?|ftp):\\\\/)\\\\/?([^:\\\\/\\\\s]+)(:[0-9]{1,5})?/)]"
    -    } ],
    -    "headers" : {
    -      "Content-Type" : {
    -        "equalTo" : "application/json"
    -      }
    -    }
    -  },
    -  "response" : {
    -    "status" : 404,
    -    "body" : "{\\"code\\":\\"123123\\",\\"message\\":\\"User not found by email == [not.existing@user.com]\\"}",
    -    "headers" : {
    -      "Content-Type" : "application/json"
    -    }
    -  },
    -  "priority" : 1
    -}
    -'''
    -
    -
    -
    -
    -

    Executing custom methods on server side

    -
    -

    It is also possible to define a method call to be executed on the server side during the test. Such a method can be added to the class defined as "baseClassForTests" -in the configuration. Please see the examples below:

    -
    Contract DSL
    +
    JAR versioning
    +
    +

    If by versioning you mean the version of the JAR that contains the stubs then there are essentially two main approaches.

    +
    +
    +

    Let’s assume that you’re doing Continuous Delivery / Deployment which means that you’re generating a new version of +the jar each time you go through the pipeline and that jar can go to production at any time. For example your jar version +looks like this (it got built on the 20.10.2016 at 20:15:21) :

    +
    -
    org.springframework.cloud.contract.spec.Contract.make {
    -	request {
    -		method 'PUT'
    -		url $(consumer(regex('^/api/[0-9]{2}$')), producer('/api/12'))
    -		headers {
    -			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 200
    -	}
    -}
    +
    1.0.0.20161020-201521-RELEASE
    +
    +
    +
    +

    In that case your generated stub jar will look like this.

    +
    +
    +
    +
    1.0.0.20161020-201521-RELEASE-stubs.jar
    +
    +
    +
    +

    In this case you should inside your application.yml or @AutoConfigureStubRunner when referencing stubs provide the + latest version of the stubs. You can do that by passing the + sign. Example

    +
    +
    +
    +
    @AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:8080"})
    +
    +
    +
    +

    If the versioning however is fixed (e.g. 1.0.4.RELEASE or 2.1.1) then you have to set the concrete value of the jar +version. Example for 2.1.1.

    +
    +
    +
    +
    @AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:2.1.1:stubs:8080"})
    -
    Base Mock Spec
    -
    -
    -
    abstract class BaseMockMvcSpec extends Specification {
    -
    -	def setup() {
    -		RestAssuredMockMvc.standaloneSetup(new PairIdController())
    -	}
    -
    -	void isProperCorrelationId(Integer correlationId) {
    -		assert correlationId == 123456
    -	}
    -
    -	void isEmpty(String value) {
    -		assert value == null
    -	}
    -
    -}
    -
    -
    -
    -
    -
    -

    JAX-RS support

    +
    Dev or prod stubs
    -

    Starting with release 0.8.0 we support JAX-RS 2 Client API. Base class needs to define protected WebTarget webTarget and server initialization, right now the only option how to test JAX-RS API is to start a web server.

    +

    You can manipulate the classifier to run the tests against current development version of the stubs of other services + or the ones that were deployed to production. If you alter your build to deploy the stubs with the prod-stubs classifier + once you reach production deployment then you can run tests in one case with dev stubs and one with prod stubs.

    -

    Request with a body needs to have a content type set otherwise application/octet-stream is going to be used.

    -
    -
    -

    In order to use JAX-RS mode, use the following settings:

    +

    Example of tests using development version of stubs

    -
    testMode === 'JAXRSCLIENT'
    +
    @AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:8080"})
    -

    Example of a test API generated:

    +

    Example of tests using production version of stubs

    -
    '''
    - // when:
    -  Response response = webTarget
    -    .path("/users")
    -    .queryParam("limit", "10")
    -    .queryParam("offset", "20")
    -    .queryParam("filter", "email")
    -    .queryParam("sort", "name")
    -    .queryParam("search", "55")
    -    .queryParam("age", "99")
    -    .queryParam("name", "Denis.Stepanov")
    -    .queryParam("email", "bob@email.com")
    -    .request()
    -    .method("GET");
    -
    -  String responseAsString = response.readEntity(String.class);
    -
    - // then:
    -  assertThat(response.getStatus()).isEqualTo(200);
    - // and:
    -  DocumentContext parsedJson = JsonPath.parse(responseAsString);
    -  assertThatJson(parsedJson).field("property1").isEqualTo("a");
    -'''
    -
    -
    -
    -
    -

    Messaging Top-Level Elements

    -
    -

    The DSL for messaging looks a little bit different than the one that focuses on HTTP.

    -
    -
    -
    Output triggered by a method
    -
    -

    The output message can be triggered by calling a method (e.g. a Scheduler was started and a message was sent)

    -
    -
    -
    -
    def dsl = Contract.make {
    -	// Human readable description
    -	description 'Some description'
    -	// Label by means of which the output message can be triggered
    -	label 'some_label'
    -	// input to the contract
    -	input {
    -		// the contract will be triggered by a method
    -		triggeredBy('bookReturnedTriggered()')
    -	}
    -	// output message of the contract
    -	outputMessage {
    -		// destination to which the output message will be sent
    -		sentTo('output')
    -		// the body of the output message
    -		body('''{ "bookName" : "foo" }''')
    -		// the headers of the output message
    -		headers {
    -			header('BOOK-NAME', 'foo')
    -		}
    -	}
    -}
    +
    @AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:prod-stubs:8080"})
    -

    In this case the output message will be sent to output if a method called bookReturnedTriggered will be executed. In the message publisher’s side -we will generate a test that will call that method to trigger the message. On the consumer side you can use the some_label to trigger the message.

    -
    -
    -
    -
    Output triggered by a message
    -
    -

    The output message can be triggered by receiving a message.

    -
    -
    -
    -
    def dsl = Contract.make {
    -	description 'Some Description'
    -	label 'some_label'
    -	// input is a message
    -	input {
    -		// the message was received from this destination
    -		messageFrom('input')
    -		// has the following body
    -		messageBody([
    -		        bookName: 'foo'
    -		])
    -		// and the following headers
    -		messageHeaders {
    -			header('sample', 'header')
    -		}
    -	}
    -	outputMessage {
    -		sentTo('output')
    -		body([
    -		        bookName: 'foo'
    -		])
    -		headers {
    -			header('BOOK-NAME', 'foo')
    -		}
    -	}
    -}
    -
    -
    -
    -

    In this case the output message will be sent to output if a proper message will be received on the input destination. In the message publisher’s side -we will generate a test that will send the input message to the defined destination. On the consumer side you can either send a message to the input -destination or use the some_label to trigger the message.

    -
    -
    -
    -
    Consumer / Producer
    -
    -

    In HTTP you have a notion of client/stub and `server/test notation. You can use them also in messaging but we’re providing also the consumer and produer methods -as presented below (note you can use either $ or value methods to provide consumer and producer parts)

    -
    -
    -
    -
    Contract.make {
    -	label 'some_label'
    -	input {
    -		messageFrom value(consumer('jms:output'), producer('jms:input'))
    -		messageBody([
    -				bookName: 'foo'
    -		])
    -		messageHeaders {
    -			header('sample', 'header')
    -		}
    -	}
    -	outputMessage {
    -		sentTo $(consumer('jms:input'), producer('jms:output'))
    -		body([
    -				bookName: 'foo'
    -		])
    -	}
    -}
    -
    +

    You can pass those values also via properties from your deployment pipeline.

    @@ -5115,6 +4608,836 @@ receivedMessage.headers.get('BOOK-NAME') == 'foo'
    +

    Contract DSL

    +
    + + + + + +
    +
    Important
    +
    +Remember that inside the contract file you have to provide the fully qualified name to +the Contract class and the make static import i.e. 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 { …​ } +
    +
    +
    +

    Contract DSL is written in Groovy, but don’t be alarmed if you didn’t use Groovy before. Knowledge of the language is not really needed as our DSL uses only +a tiny subset of it (namely literals, method calls and closures). What’s more the DSL is designed to be programmer-readable without any knowledge of the DSL itself - + it’s statically typed.

    +
    +
    +

    The Contract is present in the spring-cloud-contract-spec module of the Spring Cloud Contract Verifier repository.

    +
    +
    +

    Let’s look at full example of a 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 200
    +	}
    +}
    +
    +
    +
    +

    Not all features of the DSL are used in example above. If you didn’t find what you are looking for, please check next paragraphs on this page.

    +
    +
    +
    +
    +

    You can easily compile Contracts to WireMock stubs mapping using standalone maven command: mvn org.springframework.cloud:spring-cloud-contract-maven-plugin:convert.

    +
    +
    +
    +
    +

    Limitations

    +
    + + + + + +
    +
    Warning
    +
    +Spring Cloud Contract Verifier doesn’t support XML properly. Please use JSON or help us implement this feature. +
    +
    +
    + + + + + +
    +
    Warning
    +
    +Spring Cloud Contract Verifier supports equality check on text response. Regular expressions are not yet available. +
    +
    +
    + + + + + +
    +
    Warning
    +
    +The support for the verification of size of JSON arrays is experimental. If you want to turn it on please provide +the value of a system property spring.cloud.contract.verifier.assert.size equal to true. By default this feature is set to +false. You can also provide the assertJsonSize property in the plugin configuration. +
    +
    +
    + + + + + +
    +
    Warning
    +
    +Due to the fact that JSON structure can have any form it’s sometimes impossible to parse it properly when using +the value(consumer(…​), producer(…​)) notation when using that in GString. That’s why we highly recommend using the +Groovy Map notation. +
    +
    +
    +
    +

    HTTP Top-Level Elements

    +
    +

    Following methods can be called in the top-level closure of a contract definition. Request and response are mandatory, priority is optional.

    +
    +
    +
    +
    org.springframework.cloud.contract.spec.Contract.make {
    +	// Definition of HTTP request part of the contract
    +	// (this can be a valid request or invalid depending
    +	// on type of contract being specified).
    +	request {
    +		//...
    +	}
    +
    +	// Definition of HTTP response part of the contract
    +	// (a service implementing this contract should respond
    +	// with following response after receiving request
    +	// specified in "request" part above).
    +	response {
    +		//...
    +	}
    +
    +	// Contract priority, which can be used for overriding
    +	// contracts (1 is highest). Priority is optional.
    +	priority 1
    +}
    +
    +
    +
    +
    +

    Request

    +
    +

    HTTP protocol requires only method and address to be specified in a request. The same information is mandatory in request definition of the Contract.

    +
    +
    +
    +
    org.springframework.cloud.contract.spec.Contract.make {
    +	request {
    +		// HTTP request method (GET/POST/PUT/DELETE).
    +		method 'GET'
    +
    +		// Path component of request URL is specified as follows.
    +		urlPath('/users')
    +	}
    +
    +	response {
    +		//...
    +	}
    +}
    +
    +
    +
    +

    It is possible to specify whole url instead of just path, but urlPath is the recommended way as it makes the tests host-independent.

    +
    +
    +
    +
    org.springframework.cloud.contract.spec.Contract.make {
    +	request {
    +		method 'GET'
    +
    +		// Specifying `url` and `urlPath` in one contract is illegal.
    +		url('http://localhost:8888/users')
    +	}
    +
    +	response {
    +		//...
    +	}
    +}
    +
    +
    +
    +

    Request may contain query parameters, which are specified in a closure nested in a call to urlPath or url.

    +
    +
    +
    +
    org.springframework.cloud.contract.spec.Contract.make {
    +	request {
    +		//...
    +
    +		urlPath('/users') {
    +
    +			// Each parameter is specified in form
    +			// `'paramName' : paramValue` where parameter value
    +			// may be a simple literal or one of matcher functions,
    +			// all of which are used in this example.
    +			queryParameters {
    +
    +				// If a simple literal is used as value
    +				// default matcher function is used (equalTo)
    +				parameter 'limit': 100
    +
    +				// `equalTo` function simply compares passed value
    +				// using identity operator (==).
    +				parameter 'filter': equalTo("email")
    +
    +				// `containing` function matches strings
    +				// that contains passed substring.
    +				parameter 'gender': value(consumer(containing("[mf]")), producer('mf'))
    +
    +				// `matching` function tests parameter
    +				// against passed regular expression.
    +				parameter 'offset': value(consumer(matching("[0-9]+")), producer(123))
    +
    +				// `notMatching` functions tests if parameter
    +				// does not match passed regular expression.
    +				parameter 'loginStartsWith': value(consumer(notMatching(".{0,2}")), producer(3))
    +			}
    +		}
    +
    +		//...
    +	}
    +
    +	response {
    +		//...
    +	}
    +}
    +
    +
    +
    +

    It may contain additional request headers…​

    +
    +
    +
    +
    org.springframework.cloud.contract.spec.Contract.make {
    +	request {
    +		//...
    +
    +		// Each header is added in form `'Header-Name' : 'Header-Value'`.
    +		headers {
    +			header 'Content-Type': 'application/json'
    +		}
    +
    +		//...
    +	}
    +
    +	response {
    +		//...
    +	}
    +}
    +
    +
    +
    +

    …​and a request body.

    +
    +
    +
    +
    org.springframework.cloud.contract.spec.Contract.make {
    +	request {
    +		//...
    +
    +		// Currently only JSON format of request body is supported.
    +		// Format will be determined from a header or body's content.
    +		body '''{ "login" : "john", "name": "John The Contract" }'''
    +	}
    +
    +	response {
    +		//...
    +	}
    +}
    +
    +
    +
    +
    +

    Response

    +
    +

    Minimal response must contain HTTP status code.

    +
    +
    +
    +
    org.springframework.cloud.contract.spec.Contract.make {
    +	request {
    +		//...
    +	}
    +	response {
    +		// Status code sent by the server
    +		// in response to request specified above.
    +		status 200
    +	}
    +}
    +
    +
    +
    +

    Besides status response may contain headers and body, which are specified the same way as in the request (see previous paragraph).

    +
    +
    +
    +

    Dynamic properties

    +
    +

    The contract can contain some dynamic properties - timestamps / ids etc. You don’t want to enforce the consumers to stub their +clocks to always return the same value of time so that it gets matched by the stub. That’s why we allow you to provide the dynamic +parts in your contracts in the following way

    +
    +
    +

    either via the value method

    +
    +
    +
    +
    value(consumer(...), producer(...))
    +value(stub(...), test(...))
    +value(client(...), server(...))
    +
    +
    +
    +

    or if you’re using the Groovy map notation for body you can use the $() method

    +
    +
    +
    +
    $(consumer(...), producer(...))
    +$(stub(...), test(...))
    +$(client(...), server(...))
    +
    +
    +
    +

    All of the aforementioned approaches are equal. That means that stub and client methods are aliases over the consumer +method. Let’s take a closer look at what we can do with those values in the subsequent sections.

    +
    +
    +
    +

    Regular expressions

    +
    +

    You can use regular expressions to write your requests in Contract DSL. It is particularly useful when you want to indicate that a given response +should be provided for requests that follow a given pattern. Also, you can use it when you need to use patterns and not exact values both +for your test and your server side tests.

    +
    +
    +

    Please see the example below:

    +
    +
    +
    +
    org.springframework.cloud.contract.spec.Contract.make {
    +	request {
    +		method('GET')
    +		url $(consumer(~/\/[0-9]{2}/), producer('/12'))
    +	}
    +	response {
    +		status 200
    +		body(
    +				id: value(
    +						consumer('123'),
    +						producer(regex('[0-9]+'))
    +				),
    +				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}'))
    +				)
    +		)
    +		headers {
    +			header 'Content-Type': 'text/plain'
    +		}
    +	}
    +}
    +
    +
    +
    +

    You can also provide only one side of the communication using a regular expression. If you do that then automatically we’ll +provide the generated string that matches the provided regular expression. For example:

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

    In this example for request and response the opposite side of the communication will have the respective data generated.

    +
    +
    +

    Spring Cloud Contract comes with a series of predefined regular expressions that you can use in your contracts.

    +
    +
    +
    +
    private static final Pattern TRUE_OR_FALSE = Pattern.compile(/(true|false)/)
    +private static final Pattern ONLY_ALPHA_UNICODE = Pattern.compile(/[\p{L}]*/)
    +private static final Pattern NUMBER = Pattern.compile('-?\\d*(\\.\\d+)?')
    +private static final Pattern IP_ADDRESS = 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])');
    +private static final Pattern HOSTNAME_PATTERN = Pattern.compile('((http[s]?|ftp):\\/)\\/?([^:\\/\\s]+)(:[0-9]{1,5})?');
    +private static final Pattern EMAIL = Pattern.compile('[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,4}');
    +private static final Pattern URL = Pattern.compile('((www\\.|(http|https|ftp|news|file)+\\:\\/\\/)[_.a-z0-9-]+\\.[a-z0-9\\/_:@=.+?,##%&~-]*[^.|\\\'|\\# |!|\\(|?|,| |>|<|;|\\)])')
    +
    +String onlyAlphaUnicode() {
    +	return ONLY_ALPHA_UNICODE.pattern()
    +}
    +
    +String number() {
    +	return NUMBER.pattern()
    +}
    +
    +String anyBoolean() {
    +	return TRUE_OR_FALSE.pattern()
    +}
    +
    +String ipAddress() {
    +	return IP_ADDRESS.pattern()
    +}
    +
    +String hostname() {
    +	return HOSTNAME_PATTERN.pattern()
    +}
    +
    +String email() {
    +	return EMAIL.pattern()
    +}
    +
    +String url() {
    +	return URL.pattern()
    +}
    +
    +
    +
    +

    so in your contract you can use it like this

    +
    +
    +
    +
    Contract dslWithOptionalsInString = Contract.make {
    +	priority 1
    +	request {
    +		method 'POST'
    +		url '/users/password'
    +		headers {
    +			header 'Content-Type': 'application/json'
    +		}
    +		body(
    +				email: $(consumer(optional(regex(email()))), producer('abc@abc.com')),
    +				callback_url: $(consumer(regex(hostname())), producer('http://partners.com'))
    +		)
    +	}
    +	response {
    +		status 404
    +		headers {
    +			header 'Content-Type': 'application/json'
    +		}
    +		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

    +
    +

    It is possible to provide optional parameters in your contract. It’s only possible to have optional parameter for the:

    +
    +
    +
      +
    • +

      STUB side of the Request

      +
    • +
    • +

      TEST side of the Response

      +
    • +
    +
    +
    +

    Example:

    +
    +
    +
    +
    org.springframework.cloud.contract.spec.Contract.make {
    +	priority 1
    +	request {
    +		method 'POST'
    +		url '/users/password'
    +		headers {
    +			header 'Content-Type': 'application/json'
    +		}
    +		body(
    +				email: $(consumer(optional(regex(email()))), producer('abc@abc.com')),
    +				callback_url: $(consumer(regex(hostname())), producer('http://partners.com'))
    +		)
    +	}
    +	response {
    +		status 404
    +		headers {
    +			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 are in fact creating a regular expression that should be present 0 or more times.

    +
    +
    +

    That way for the example above the following test would be generated if you pick Spock:

    +
    +
    +
    +
    """
    + given:
    +  def request = given()
    +    .header('Content-Type', 'application/json')
    +    .body('''{"email":"abc@abc.com","callback_url":"http://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)?")
    +"""
    +
    +
    +
    +

    and the following stub:

    +
    +
    +
    +
    '''
    +{
    +  "request" : {
    +    "url" : "/users/password",
    +    "method" : "POST",
    +    "bodyPatterns" : [ {
    +      "matchesJsonPath" : "$[?(@.email =~ /([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,4})?/)]"
    +    }, {
    +      "matchesJsonPath" : "$[?(@.callback_url =~ /((http[s]?|ftp):\\\\/)\\\\/?([^:\\\\/\\\\s]+)(:[0-9]{1,5})?/)]"
    +    } ],
    +    "headers" : {
    +      "Content-Type" : {
    +        "equalTo" : "application/json"
    +      }
    +    }
    +  },
    +  "response" : {
    +    "status" : 404,
    +    "body" : "{\\"code\\":\\"123123\\",\\"message\\":\\"User not found by email == [not.existing@user.com]\\"}",
    +    "headers" : {
    +      "Content-Type" : "application/json"
    +    }
    +  },
    +  "priority" : 1
    +}
    +'''
    +
    +
    +
    +
    +

    Executing custom methods on server side

    +
    +

    It is also possible to define a method call to be executed on the server side during the test. Such a method can be added to the class defined as "baseClassForTests" +in the configuration. Please see the examples below:

    +
    +
    +
    Contract DSL
    +
    +
    +
    org.springframework.cloud.contract.spec.Contract.make {
    +	request {
    +		method 'PUT'
    +		url $(consumer(regex('^/api/[0-9]{2}$')), producer('/api/12'))
    +		headers {
    +			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 200
    +	}
    +}
    +
    +
    +
    +
    +
    Base Mock Spec
    +
    +
    +
    abstract class BaseMockMvcSpec extends Specification {
    +
    +	def setup() {
    +		RestAssuredMockMvc.standaloneSetup(new PairIdController())
    +	}
    +
    +	void isProperCorrelationId(Integer correlationId) {
    +		assert correlationId == 123456
    +	}
    +
    +	void isEmpty(String value) {
    +		assert value == null
    +	}
    +
    +}
    +
    +
    +
    +
    +
    +

    JAX-RS support

    +
    +

    Starting with release 0.8.0 we support JAX-RS 2 Client API. Base class needs to define protected WebTarget webTarget and server initialization, right now the only option how to test JAX-RS API is to start a web server.

    +
    +
    +

    Request with a body needs to have a content type set otherwise application/octet-stream is going to be used.

    +
    +
    +

    In order to use JAX-RS mode, use the following settings:

    +
    +
    +
    +
    testMode === 'JAXRSCLIENT'
    +
    +
    +
    +

    Example of a test API generated:

    +
    +
    +
    +
    '''
    + // when:
    +  Response response = webTarget
    +    .path("/users")
    +    .queryParam("limit", "10")
    +    .queryParam("offset", "20")
    +    .queryParam("filter", "email")
    +    .queryParam("sort", "name")
    +    .queryParam("search", "55")
    +    .queryParam("age", "99")
    +    .queryParam("name", "Denis.Stepanov")
    +    .queryParam("email", "bob@email.com")
    +    .request()
    +    .method("GET");
    +
    +  String responseAsString = response.readEntity(String.class);
    +
    + // then:
    +  assertThat(response.getStatus()).isEqualTo(200);
    + // and:
    +  DocumentContext parsedJson = JsonPath.parse(responseAsString);
    +  assertThatJson(parsedJson).field("property1").isEqualTo("a");
    +'''
    +
    +
    +
    +
    +

    Messaging Top-Level Elements

    +
    +

    The DSL for messaging looks a little bit different than the one that focuses on HTTP.

    +
    +
    +
    Output triggered by a method
    +
    +

    The output message can be triggered by calling a method (e.g. a Scheduler was started and a message was sent)

    +
    +
    +
    +
    def dsl = Contract.make {
    +	// Human readable description
    +	description 'Some description'
    +	// Label by means of which the output message can be triggered
    +	label 'some_label'
    +	// input to the contract
    +	input {
    +		// the contract will be triggered by a method
    +		triggeredBy('bookReturnedTriggered()')
    +	}
    +	// output message of the contract
    +	outputMessage {
    +		// destination to which the output message will be sent
    +		sentTo('output')
    +		// the body of the output message
    +		body('''{ "bookName" : "foo" }''')
    +		// the headers of the output message
    +		headers {
    +			header('BOOK-NAME', 'foo')
    +		}
    +	}
    +}
    +
    +
    +
    +

    In this case the output message will be sent to output if a method called bookReturnedTriggered will be executed. In the message publisher’s side +we will generate a test that will call that method to trigger the message. On the consumer side you can use the some_label to trigger the message.

    +
    +
    +
    +
    Output triggered by a message
    +
    +

    The output message can be triggered by receiving a message.

    +
    +
    +
    +
    def dsl = Contract.make {
    +	description 'Some Description'
    +	label 'some_label'
    +	// input is a message
    +	input {
    +		// the message was received from this destination
    +		messageFrom('input')
    +		// has the following body
    +		messageBody([
    +		        bookName: 'foo'
    +		])
    +		// and the following headers
    +		messageHeaders {
    +			header('sample', 'header')
    +		}
    +	}
    +	outputMessage {
    +		sentTo('output')
    +		body([
    +		        bookName: 'foo'
    +		])
    +		headers {
    +			header('BOOK-NAME', 'foo')
    +		}
    +	}
    +}
    +
    +
    +
    +

    In this case the output message will be sent to output if a proper message will be received on the input destination. In the message publisher’s side +we will generate a test that will send the input message to the defined destination. On the consumer side you can either send a message to the input +destination or use the some_label to trigger the message.

    +
    +
    +
    +
    Consumer / Producer
    +
    +

    In HTTP you have a notion of client/stub and `server/test notation. You can use them also in messaging but we’re providing also the consumer and produer methods +as presented below (note you can use either $ or value methods to provide consumer and producer parts)

    +
    +
    +
    +
    Contract.make {
    +	label 'some_label'
    +	input {
    +		messageFrom value(consumer('jms:output'), producer('jms:input'))
    +		messageBody([
    +				bookName: 'foo'
    +		])
    +		messageHeaders {
    +			header('sample', 'header')
    +		}
    +	}
    +	outputMessage {
    +		sentTo $(consumer('jms:input'), producer('jms:output'))
    +		body([
    +				bookName: 'foo'
    +		])
    +	}
    +}
    +
    +
    +
    +
    +
    +

    Here you can find interesting links related to Spring Cloud Contract Verifier:

    @@ -5464,7 +5787,7 @@ number of different ways, including as described above using