Changed the docs (#74)

* reorganized the entries (now the Contract details went to the very bottom)
* added FAQ section where versioning and dynamic props are better defined
* added in a couple of places links to Groovy JSON docs

fixes #69
This commit is contained in:
Marcin Grzejszczak
2016-09-06 19:41:34 +02:00
committed by GitHub
parent 28e8cc538e
commit dbc7c57702
4 changed files with 463 additions and 85 deletions

View File

@@ -365,7 +365,6 @@ image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-contract/mast
Spring Cloud Contract Verifier gives you the certainty that the stubs that you're using were created by the service that you're calling. Also if you can use them it means that they were
tested against the producer's side. In other words - you can trust those stubs.
==== Purposes
The main purposes of Spring Cloud Contract Verifier with Stub Runner are:
@@ -375,6 +374,11 @@ The main purposes of Spring Cloud Contract Verifier with Stub Runner are:
- to provide a way to publish changes in contracts that are immediately visible on both sides,
- to generate boilerplate test code used on the server side.
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
During the tests you want to have a WireMock instance / Messaging route up and running that simulates the service Y.
@@ -618,6 +622,9 @@ either by means of a map notation or String with interpolations.
https://cloud.spring.io/spring-cloud-contract/spring-cloud-contract.html#_contract_dsl[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
http://groovy-lang.org/json.html[Groovy docs regarding JSON]
The aforementioned contract is an agreement between two sides that:
- if an HTTP request is sent with
@@ -800,7 +807,7 @@ You have to add the dependencies needed by the autogenerated tests
[source,xml,indent=0]
----
<dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-verifier</artifactId>
<scope>test</scope>
@@ -984,11 +991,14 @@ https://vimeo.com/130779882[click here to see the video]
- http://www.slideshare.net/MarcinGrzejszczak/stick-to-the-rules-consumer-driven-contracts-201507-confitura[Slides from Marcin Grzejszczak's talk about Accurest]
- http://toomuchcoding.com/blog/categories/accurest/[Accurest related articles from Marcin Grzejszczak's blog]
- http://toomuchcoding.com/blog/categories/spring-cloud-contract/[Spring Cloud Contract related articles from Marcin Grzejszczak's blog]
- http://groovy-lang.org/json.html[Groovy docs regarding JSON]
==== Samples
Here you can find some https://github.com/spring-cloud/spring-cloud-contract/tree/master/samples/[samples].
=== FAQ
==== Why use Spring Cloud Contract Verifier and not X ?
For the time being Spring Cloud Contract Verifier is a JVM based tool. So it could be your first pick when you're already creating
@@ -1002,6 +1012,225 @@ Spring Cloud Contract Verifier stand out on the "market" of Consumer Driven Cont
- Stub Runner functionality - the stubs are automatically downloaded at runtime from Nexus / Artifactory
- Spring Cloud integration - no discovery service is needed for integration tests
==== What is this value(consumer(), producer()) ?
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
[source,json,indent=0]
----
{
"time" : "2016-10-10 20:10:15",
"id" : "9febab1c-6f36-4a0b-88d6-3b6a6d81cd4a",
"body" : "foo"
}
----
and JSON response
[source,json,indent=0]
----
{
"time" : "2016-10-10 21:10:15",
"id" : "c4231e1f-3ca9-48d3-b7e7-567d55f0d051",
"body" : "bar"
}
----
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:
[source,json,indent=0]
----
{
"time" : "SOMETHING THAT MATCHES TIME",
"id" : "SOMETHING THAT MATCHES UUID",
"body" : "foo"
}
----
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
[source,json,indent=0]
----
{
"time" : "2016-10-10 21:10:15",
"id" : "c4231e1f-3ca9-48d3-b7e7-567d55f0d051",
"body" : "bar"
}
----
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:
[source,json,indent=0]
----
{
"time" : "2016-10-10 20:10:15",
"id" : "9febab1c-6f36-4a0b-88d6-3b6a6d81cd4a",
"body" : "foo"
}
----
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:
[source,json,indent=0]
----
{
"time" : "SOMETHING THAT MATCHES TIME",
"id" : "SOMETHING THAT MATCHES UUID",
"body" : "bar"
}
----
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
[source,groovy,indent=0]
----
value(consumer(...), producer(...))
value(stub(...), test(...))
value(client(...), server(...))
----
or using the `$()` method
[source,groovy,indent=0]
----
$(consumer(...), producer(...))
$(stub(...), test(...))
$(client(...), server(...))
----
You can read more about this in the https://cloud.spring.io/spring-cloud-contract/spring-cloud-contract.html#_contract_dsl[Contract DSL section].
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):
[source,groovy,indent=0]
----
org.springframework.cloud.contract.spec.Contract.make {
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"
])
}
}
----
IMPORTANT: Please read the http://groovy-lang.org/json.html[Groovy docs related to JSON] to understand how to
properly structure the request / response bodies.
==== How to do Stubs versioning?
===== API Versioning
Let's try to answer a question what versioning really means. If you're referring to the API version then there are
different approaches.
- use Hypermedia, links and do not version your API by any means
- pass versions through headers / urls
I will not try to answer a question which approach is better. Whatever suit your needs and allows you to generate
business value should be picked.
Let's assume that you do version your API. In that case you should provide as many contracts as many versions you support.
You can create a subfolder for every version or append it to th contract name - whatever suits you more.
===== 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) :
[source,groovy,indent=0]
----
1.0.0.20161020-201521-RELEASE
----
In that case your generated stub jar will look like this.
[source,groovy,indent=0]
----
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
[source,java,indent=0]
----
@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.
[source,java,indent=0]
----
@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:2.1.1:stubs:8080"})
----
===== Dev or prod stubs
You can manipulate the classifier to run the tests against current development version of the stubs of other services
or the ones that were deployed to production. If you alter your build to deploy the stubs with the `prod-stubs` classifier
once you reach production deployment then you can run tests in one case with dev stubs and one with prod stubs.
Example of tests using development version of stubs
[source,java,indent=0]
----
@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:8080"})
----
Example of tests using production version of stubs
[source,java,indent=0]
----
@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:prod-stubs:8080"})
----
You can pass those values also via properties from your deployment pipeline.
=== Links
Here you can find interesting links related to Spring Cloud Contract Verifier:

View File

@@ -71,7 +71,6 @@ image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-contract/mast
Spring Cloud Contract Verifier gives you the certainty that the stubs that you're using were created by the service that you're calling. Also if you can use them it means that they were
tested against the producer's side. In other words - you can trust those stubs.
==== Purposes
The main purposes of Spring Cloud Contract Verifier with Stub Runner are:
@@ -81,6 +80,11 @@ The main purposes of Spring Cloud Contract Verifier with Stub Runner are:
- to provide a way to publish changes in contracts that are immediately visible on both sides,
- to generate boilerplate test code used on the server side.
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
During the tests you want to have a WireMock instance / Messaging route up and running that simulates the service Y.
@@ -187,6 +191,9 @@ either by means of a map notation or String with interpolations.
https://cloud.spring.io/spring-cloud-contract/spring-cloud-contract.html#_contract_dsl[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
http://groovy-lang.org/json.html[Groovy docs regarding JSON]
The aforementioned contract is an agreement between two sides that:
- if an HTTP request is sent with
@@ -475,11 +482,14 @@ https://vimeo.com/130779882[click here to see the video]
- http://www.slideshare.net/MarcinGrzejszczak/stick-to-the-rules-consumer-driven-contracts-201507-confitura[Slides from Marcin Grzejszczak's talk about Accurest]
- http://toomuchcoding.com/blog/categories/accurest/[Accurest related articles from Marcin Grzejszczak's blog]
- http://toomuchcoding.com/blog/categories/spring-cloud-contract/[Spring Cloud Contract related articles from Marcin Grzejszczak's blog]
- http://groovy-lang.org/json.html[Groovy docs regarding JSON]
==== Samples
Here you can find some https://github.com/spring-cloud/spring-cloud-contract/tree/master/samples/[samples].
=== FAQ
==== Why use Spring Cloud Contract Verifier and not X ?
For the time being Spring Cloud Contract Verifier is a JVM based tool. So it could be your first pick when you're already creating
@@ -492,3 +502,222 @@ Spring Cloud Contract Verifier stand out on the "market" of Consumer Driven Cont
- Automatic generation of tests from the defined Contract
- Stub Runner functionality - the stubs are automatically downloaded at runtime from Nexus / Artifactory
- Spring Cloud integration - no discovery service is needed for integration tests
==== What is this value(consumer(), producer()) ?
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
[source,json,indent=0]
----
{
"time" : "2016-10-10 20:10:15",
"id" : "9febab1c-6f36-4a0b-88d6-3b6a6d81cd4a",
"body" : "foo"
}
----
and JSON response
[source,json,indent=0]
----
{
"time" : "2016-10-10 21:10:15",
"id" : "c4231e1f-3ca9-48d3-b7e7-567d55f0d051",
"body" : "bar"
}
----
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:
[source,json,indent=0]
----
{
"time" : "SOMETHING THAT MATCHES TIME",
"id" : "SOMETHING THAT MATCHES UUID",
"body" : "foo"
}
----
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
[source,json,indent=0]
----
{
"time" : "2016-10-10 21:10:15",
"id" : "c4231e1f-3ca9-48d3-b7e7-567d55f0d051",
"body" : "bar"
}
----
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:
[source,json,indent=0]
----
{
"time" : "2016-10-10 20:10:15",
"id" : "9febab1c-6f36-4a0b-88d6-3b6a6d81cd4a",
"body" : "foo"
}
----
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:
[source,json,indent=0]
----
{
"time" : "SOMETHING THAT MATCHES TIME",
"id" : "SOMETHING THAT MATCHES UUID",
"body" : "bar"
}
----
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
[source,groovy,indent=0]
----
value(consumer(...), producer(...))
value(stub(...), test(...))
value(client(...), server(...))
----
or using the `$()` method
[source,groovy,indent=0]
----
$(consumer(...), producer(...))
$(stub(...), test(...))
$(client(...), server(...))
----
You can read more about this in the https://cloud.spring.io/spring-cloud-contract/spring-cloud-contract.html#_contract_dsl[Contract DSL section].
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):
[source,groovy,indent=0]
----
org.springframework.cloud.contract.spec.Contract.make {
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"
])
}
}
----
IMPORTANT: Please read the http://groovy-lang.org/json.html[Groovy docs related to JSON] to understand how to
properly structure the request / response bodies.
==== How to do Stubs versioning?
===== API Versioning
Let's try to answer a question what versioning really means. If you're referring to the API version then there are
different approaches.
- use Hypermedia, links and do not version your API by any means
- pass versions through headers / urls
I will not try to answer a question which approach is better. Whatever suit your needs and allows you to generate
business value should be picked.
Let's assume that you do version your API. In that case you should provide as many contracts as many versions you support.
You can create a subfolder for every version or append it to th contract name - whatever suits you more.
===== 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) :
[source,groovy,indent=0]
----
1.0.0.20161020-201521-RELEASE
----
In that case your generated stub jar will look like this.
[source,groovy,indent=0]
----
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
[source,java,indent=0]
----
@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.
[source,java,indent=0]
----
@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:2.1.1:stubs:8080"})
----
===== Dev or prod stubs
You can manipulate the classifier to run the tests against current development version of the stubs of other services
or the ones that were deployed to production. If you alter your build to deploy the stubs with the `prod-stubs` classifier
once you reach production deployment then you can run tests in one case with dev stubs and one with prod stubs.
Example of tests using development version of stubs
[source,java,indent=0]
----
@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:8080"})
----
Example of tests using production version of stubs
[source,java,indent=0]
----
@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:prod-stubs:8080"})
----
You can pass those values also via properties from your deployment pipeline.

View File

@@ -10,8 +10,6 @@
include::introduction.adoc[]
include::contract.adoc[]
include::rest.adoc[]
include::messaging.adoc[]
@@ -20,4 +18,6 @@ include::stubrunner.adoc[]
include::stubrunner_msg.adoc[]
include::contract.adoc[]
include::links.adoc[]

View File

@@ -1,80 +0,0 @@
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.verifier.dsl.internal
import java.util.regex.Pattern
import org.springframework.cloud.contract.spec.internal.RegexPatterns
import spock.lang.Specification
class RegexPatternsSpec extends Specification {
RegexPatterns regexPatterns = new RegexPatterns()
def "should generate a regex for ip address [#textToMatch] that is a match [#shouldMatch]"() {
expect:
shouldMatch == Pattern.compile(regexPatterns.ipAddress()).matcher(textToMatch).matches()
where:
textToMatch || shouldMatch
'123.123.123.123' || true
'a.b.' || false
}
def "should generate a regex for hostname [#textToMatch] that is a match [#shouldMatch]"() {
expect:
shouldMatch == Pattern.compile(regexPatterns.hostname()).matcher(textToMatch).matches()
where:
textToMatch || shouldMatch
'https://asd.com' || true
'https://asd.com:8080' || true
'https://localhost' || true
'https://localhost:8080' || true
'https://asd.com/asd' || false
'asd.com' || false
}
def "should generate a regex for email [#textToMatch] that is a match [#shouldMatch]"() {
expect:
shouldMatch == Pattern.compile(regexPatterns.email()).matcher(textToMatch).matches()
where:
textToMatch || shouldMatch
'asd@asd.com' || true
'a.b.' || false
}
def "should generate a regex for url [#textToMatch] that is a match [#shouldMatch]"() {
expect:
shouldMatch == Pattern.compile(regexPatterns.url()).matcher(textToMatch).matches()
where:
textToMatch || shouldMatch
'ftp://asd.com:9090/asd/a?a=b' || true
'a.b.' || false
}
def "should generate a regex for a number [#textToMatch] that is a match [#shouldMatch]"() {
expect:
shouldMatch == Pattern.compile(regexPatterns.number()).matcher(textToMatch).matches()
where:
textToMatch || shouldMatch
'1' || true
'1.0' || true
'0.1' || true
'.1' || true
'1.' || false
}
}