Files
spring-cloud-contract/docs/src/main/asciidoc/verifier_contract.adoc
2018-04-02 23:58:12 +02:00

1778 lines
64 KiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
== Contract DSL
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.
IMPORTANT: Remember that, inside the Groovy contract file, you have to provide the fully
qualified name to the `Contract` class and `make` static imports, such as
`org.springframework.cloud.spec.Contract.make { ... }`. You can also provide an import to
the `Contract` class: `import org.springframework.cloud.spec.Contract` and then call
`Contract.make { ... }`.
TIP: Spring Cloud Contract supports defining multiple contracts in a single file.
The following is a complete example of a Groovy contract definition:
[source,groovy,indent=0]
----
include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcMethodBodyBuilderSpec.groovy[tags=dsl_example,indent=0]
----
The following is a complete example of a YAML contract definition:
[source,yml,indent=0]
----
include::{verifier_core_path}/src/test/resources/yml/contract_rest.yml[indent=0]
----
TIP: You can compile contracts to stubs mapping using standalone maven command:
`mvn org.springframework.cloud:spring-cloud-contract-maven-plugin:convert`
=== Limitations
WARNING: Spring Cloud Contract Verifier does not properly support XML. Please use JSON or
help us implement this feature.
WARNING: The support for verifying the size of JSON arrays is experimental. If you want
to turn it on, please set the value of the following system property to `true`:
`spring.cloud.contract.verifier.assert.size`. By default, this feature is set to `false`.
You can also provide the `assertJsonSize` property in the plugin configuration.
WARNING: Because JSON structure can have any form, it can be impossible to parse it
properly when using the Groovy DSL and the `value(consumer(...), producer(...))` notation in `GString`. That
is why you should use the Groovy Map notation.
=== Common Top-Level elements
The following sections describe the most common top-level elements:
* <<contract-dsl-description>>
* <<contract-dsl-name>>
* <<contract-dsl-ignoring-contracts>>
* <<contract-dsl-passing-values-from-files>>
* <<contract-dsl-http-top-level-elements>>
[[contract-dsl-description]]
==== Description
You can add a `description` to your contract. The description is arbitrary text. The
following code shows an example:
.Groovy DSL
[source,groovy,indent=0]
----
include::{contract_spec_path}/src/test/groovy/org/springframework/cloud/contract/spec/internal/ContractSpec.groovy[tags=description,indent=0]
----
.YAML
[source,yml,indent=0]
----
include::{verifier_core_path}/src/test/resources/yml/contract_rest.yml[indent=0]
----
[[contract-dsl-name]]
==== Name
You can provide a name for your contract. Assume that you provided the following name:
`should register a user`. If you do so, the name of the autogenerated test is
`validate_should_register_a_user`. Also, the name of the stub in a WireMock stub is
`should_register_a_user.json`.
IMPORTANT: You must ensure that the name does not contain any characters that make the
generated test not compile. Also, remember that, if you provide the same name for
multiple contracts, your autogenerated tests fail to compile and your generated stubs
override each other.
.Groovy DSL
[source,groovy,indent=0]
----
include::{contract_spec_path}/src/test/groovy/org/springframework/cloud/contract/spec/internal/ContractSpec.groovy[tags=name,indent=0]
----
.YAML
[source,yml,indent=0]
----
include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=name,indent=0]
----
[[contract-dsl-ignoring-contracts]]
==== Ignoring Contracts
If you want to ignore a contract, you can either set a value of ignored contracts in the
plugin configuration or set the `ignored` property on the contract itself:
.Groovy DSL
[source,groovy,indent=0]
----
include::{contract_spec_path}/src/test/groovy/org/springframework/cloud/contract/spec/internal/ContractSpec.groovy[tags=ignored,indent=0]
----
.YAML
[source,yml,indent=0]
----
include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=ignored,indent=0]
----
[[contract-dsl-passing-values-from-files]]
==== Passing Values from Files
Starting with version `1.2.0`, you can pass values from files. Assume that you have the
following resources in our project.
[source,bash,indent=0]
----
└── src
    └── test
       └── resources
          └── contracts
    ├── readFromFile.groovy
    ├── request.json
    └── response.json
----
Further assume that your contract is as follows:
.Groovy DSL
[source,groovy,indent=0]
----
include::{verifier_core_path}/src/test/resources/classpath/readFromFile.groovy[indent=0]
----
.YAML
[source,yml,indent=0]
----
include::{verifier_core_path}/src/test/resources/yml/contract_from_file.yml[indent=0]
----
Further assume that the JSON files is as follows:
*request.json*
[source,json,indent=0]
----
include::{verifier_core_path}/src/test/resources/classpath/request.json[indent=0]
----
*response.json*
[source,json,indent=0]
----
include::{verifier_core_path}/src/test/resources/classpath/response.json[indent=0]
----
When test or stub generation takes place, the contents of the file is passed to the body
of a request or a response. The name of the file needs to be a file with location
relative to the folder in which the contract lays.
[[contract-dsl-http-top-level-elements]]
==== HTTP Top-Level Elements
The following methods can be called in the top-level closure of a contract definition.
`request` and `response` are mandatory. `priority` is optional.
.Groovy DSL
[source,groovy,indent=0]
----
include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[tags=http_dsl,indent=0]
----
.YAML
[source,yml,indent=0]
----
include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=priority,indent=0]
include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=request,indent=0]
...
include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=response,indent=0]
...
----
IMPORTANT: If you want to make your contract have a **higher** value of priority
you need to pass a **lower** number to the `priority` tag / method. E.g. `priority` with
value `5` has **higher** priority than `priority` with value `10`.
=== Request
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
[source,groovy,indent=0]
----
include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[tags=request,indent=0]
----
.YAML
[source,yml,indent=0]
----
include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=request_obligatory,indent=0]
----
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
[source,groovy,indent=0]
----
include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[tags=url,indent=0]
----
.YAML
[source,yml,indent=0]
----
include::{verifier_core_path}/src/test/resources/yml/contract_rest_with_path.yml[tags=url_path,indent=0]
----
`request` may contain **query parameters**.
.Groovy DSL
[source,groovy,indent=0]
----
include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[tags=urlpath,indent=0]
----
.YAML
[source,yml,indent=0]
----
include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=request,indent=0]
...
include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=query_params,indent=0]
----
`request` may contain additional **request headers**, as shown in the following example:
.Groovy DSL
[source,groovy,indent=0]
----
include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[tags=headers,indent=0]
----
.YAML
[source,yml,indent=0]
----
include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=request,indent=0]
...
include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=headers,indent=0]
----
`request` may contain a **request body**:
.Groovy DSL
[source,groovy,indent=0]
----
include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[tags=body,indent=0]
----
.YAML
[source,yml,indent=0]
----
include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=request,indent=0]
...
include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=body,indent=0]
----
`request` may contain **multipart** elements. To include multipart elements, use the
`multipart` method/section, as shown in the following examples
.Groovy DSL
[source,groovy,indent=0]
----
include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcMethodBodyBuilderSpec.groovy[tags=multipartdsl,indent=0]
----
.YAML
[source,yml,indent=0]
----
include::{verifier_core_path}/src/test/resources/yml/contract_multipart.yml[indent=0]
----
In the preceding example, we define parameters in either of two ways:
.Groovy DSL
* Directly, by using the map notation, where the value can be a dynamic property (such as
`formParameter: $(consumer(...), producer(...))`).
* By using the `named(...)` method that lets you set a named parameter. A named parameter
can set a `name` and `content`. You can call it either via a method with two arguments,
such as `named("fileName", "fileContent")`, or via a map notation, such as
`named(name: "fileName", content: "fileContent")`.
.YAML
* The multipart parameters are set via `multipart.params` section
* The named parameters (the `fileName` and `fileContent` for a given parameter name)
can be set via the `multipart.named` section. That section contains
the `paramName` (name of the parameter), `fileName` (name of the file),
`fileContent` (content of the file) fields
* The dynamic bits can be set via the `matchers.multipart` section
** for parameters use the `params` section that can accept
`regex` or a `predefined` regular expression
** for named params use the `named` section where first you
define the parameter name via `paramName` and then you can pass the
parametrization of either `fileName` or `fileContent` via
`regex` or a `predefined` regular expression
From this contract, the generated test is as follows:
[source,java,indent=0]
----
// given:
MockMvcRequestSpecification request = given()
.header("Content-Type", "multipart/form-data;boundary=AaB03x")
.param("formParameter", "\"formParameterValue\"")
.param("someBooleanParameter", "true")
.multiPart("file", "filename.csv", "file content".getBytes());
// when:
ResponseOptions response = given().spec(request)
.put("/multipart");
// then:
assertThat(response.statusCode()).isEqualTo(200);
----
The WireMock stub is as follows:
[source,json,indent=0]
----
include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/dsl/WireMockGroovyDslSpec.groovy[tags=multipartwiremock,indent=0]
----
=== Response
The response must contain an **HTTP status code** and may contain other information. The
following code shows an example:
.Groovy DSL
[source,groovy,indent=0]
----
include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[tags=response,indent=0]
----
.YAML
[source,yml,indent=0]
----
include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=response,indent=0]
...
include::{verifier_core_path}/src/test/resources/yml/contract.yml[tags=response_obligatory,indent=0]
----
Besides status, the response may contain **headers** and a **body**, both of which are
specified the same way as in the request (see the previous paragraph).
TIP: Via the Groovy DSL you can reference the `org.springframework.cloud.contract.spec.internal.HttpStatus`
methods to provide a meaningful status instead of a digit. E.g. you can call
`OK()` for a status `200` or `BAD_REQUEST()` for `400`.
=== Dynamic properties
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.
For Groovy DSL you can provide the dynamic parts in your contracts
in two ways: pass them directly in the body or set them in a separate section called
`bodyMatchers`.
NOTE: Before 2.0.0 these were set using `testMatchers` and `stubMatchers`,
check out the https://github.com/spring-cloud/spring-cloud-contract/wiki/Spring-Cloud-Contract-2.0-Migration-Guide[migration guide] for more information.
For YAML you can only use the `matchers` section.
==== Dynamic properties inside the body
IMPORTANT: This section is valid only for Groovy DSL. Check out the
<<contract-matchers>> section for YAML examples of a similar feature.
You can set the properties inside the body either with the `value` method or, if you use
the Groovy map notation, with `$()`. The following example shows how to set dynamic
properties with the value method:
[source,groovy,indent=0]
----
value(consumer(...), producer(...))
value(c(...), p(...))
value(stub(...), test(...))
value(client(...), server(...))
----
The following example shows how to set dynamic properties with `$()`:
[source,groovy,indent=0]
----
$(consumer(...), producer(...))
$(c(...), p(...))
$(stub(...), test(...))
$(client(...), server(...))
----
Both approaches work equally well. `stub` and `client` methods are aliases over the `consumer`
method. Subsequent sections take a closer look at what you can do with those values.
==== Regular expressions
IMPORTANT: This section is valid only for Groovy DSL. Check out the
<<contract-matchers>> section for YAML examples of a similar feature.
You can use regular expressions to write your requests in Contract DSL. Doing so is
particularly useful when you want to indicate that a given response should be provided
for requests that follow a given pattern. Also, you can use regular expressions when you
need to use patterns and not exact values both for your test and your server side tests.
The following example shows how to use regular expressions to write a request:
[source,groovy,indent=0]
----
include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[tags=regex,indent=0]
----
You can also provide only one side of the communication with a regular expression. If you
do so, then the contract engine automatically provides the generated string that matches
the provided regular expression. The following code shows an example:
[source,groovy,indent=0]
----
include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcMethodBodyBuilderSpec.groovy[tags=dsl_one_side_data_generation_example,indent=0]
----
In the preceding example, the opposite side of the communication has the respective data
generated for request and response.
Spring Cloud Contract comes with a series of predefined regular expressions that you can
use in your contracts, as shown in the following example:
[source,groovy,indent=0]
----
include::{contract_spec_path}/src/main/groovy/org/springframework/cloud/contract/spec/internal/RegexPatterns.groovy[tags=regexps,indent=0]
----
In your contract, you can use it as shown in the following example:
[source,groovy,indent=0]
----
include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcMethodBodyBuilderSpec.groovy[tags=contract_with_regex,indent=0]
----
==== Passing Optional Parameters
IMPORTANT: This section is valid only for Groovy DSL. Check out the
<<contract-matchers>> section for YAML examples of a similar feature.
It is possible to provide optional parameters in your contract. However, you can provide
optional parameters only for the following:
* __STUB__ side of the Request
* __TEST__ side of the Response
The following example shows how to provide optional parameters:
[source,groovy,indent=0]
----
include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[tags=optionals,indent=0]
----
By wrapping a part of the body with the `optional()` method, you create a regular
expression that must be present 0 or more times.
If you use Spock for, the following test would be generated from the previous example:
[source,groovy,indent=0]
----
include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[tags=optionals_test,indent=0]
----
The following stub would also be generated:
[source,groovy,indent=0]
----
include::{plugins_path}/spring-cloud-contract-converters/src/test/groovy/org/springframework/cloud/contract/verifier/wiremock/DslToWireMockClientConverterSpec.groovy[tags=wiremock,indent=0]
----
==== Executing Custom Methods on the Server Side
IMPORTANT: This section is valid only for Groovy DSL. Check out the
<<contract-matchers>> section for YAML examples of a similar feature.
You can define a method call that executes on the server side during the test. Such a
method can be added to the class defined as "baseClassForTests" in the configuration. The
following code shows an example of the contract portion of the test case:
[source,groovy,indent=0]
----
include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/ContractHttpDocsSpec.groovy[tags=method,indent=0]
----
The following code shows the base class portion of the test case:
[source,groovy,indent=0]
----
include::{plugins_path}/spring-cloud-contract-gradle-plugin/src/test/resources/functionalTest/bootSimple/src/test/groovy/org/springframework/cloud/contract/verifier/twitter/places/BaseMockMvcSpec.groovy[tags=base_class,indent=0]
----
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 taken from
a method.
IMPORTANT: You must provide both the consumer and the producer side. The `execute` part
is applied for the whole body - not for parts of it.
The following example shows how to read an object from JSON:
[source,groovy,indent=0]
----
include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MethodBodyBuilderSpec.groovy[tags=body_execute,indent=0]
----
The preceding example results in calling the `hashCode()` method in the request body.
It should resemble the following code:
[source,java,indent=0]
----
// given:
MockMvcRequestSpecification request = given()
.body(hashCode());
// when:
ResponseOptions response = given().spec(request)
.get("/something");
// then:
assertThat(response.statusCode()).isEqualTo(200);
----
==== Referencing 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 can use the following
options:
* `fromRequest().url()`: Returns the request URL and query parameters.
* `fromRequest().query(String key)`: Returns the first query parameter with a given name.
* `fromRequest().query(String key, int index)`: Returns the nth query parameter with a
given name.
* `fromRequest().path()`: Returns the full path.
* `fromRequest().path(int index)`: Returns the nth path element.
* `fromRequest().header(String key)`: Returns the first header with a given name.
* `fromRequest().header(String key, int index)`: Returns the nth header with a given name.
* `fromRequest().body()`: Returns the full request body.
* `fromRequest().body(String jsonPath)`: Returns the element from the request that
matches the JSON Path.
If you're using the YAML contract definition you have to use the
http://handlebarsjs.com/[Handlebars] `{{{ }}}` notation with custom, Spring Cloud Contract
functions to achieve this.
* `{{{ request.url }}}`: Returns the request URL and query parameters.
* `{{{ request.query.key.[index] }}}`: Returns the nth query parameter with a given name.
E.g. for key `foo`, first entry `{{{ request.query.foo.[0] }}}`
* `{{{ request.path }}}`: Returns the full path.
* `{{{ request.path.[index] }}}`: Returns the nth path element. E.g.
for first entry ```{{{ request.path.[0] }}}
* `{{{ request.headers.key }}}`: Returns the first header with a given name.
* `{{{ request.headers.key.[index] }}}`: Returns the nth header with a given name.
* `{{{ request.body }}}`: Returns the full request body.
* `{{{ jsonpath this 'your.json.path' }}}`: Returns the element from the request that
matches the JSON Path. E.g. for json path `$.foo` - `{{{ jsonpath this '$.foo' }}}`
Consider the following contract:
.Groovy DSL
[source,groovy,indent=0]
----
include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcMethodBodyBuilderSpec.groovy[tags=template_contract,indent=0]
----
.YAML
[source,yml,indent=0]
----
include::{verifier_core_path}/src/test/resources/yml/contract_reference_request.yml[indent=0]
----
Running a JUnit test generation leads to a test that resembles the following example:
[source,java,indent=0]
----
// given:
MockMvcRequestSpecification request = given()
.header("Authorization", "secret")
.header("Authorization", "secret2")
.body("{\"foo\":\"bar\",\"baz\":5}");
// when:
ResponseOptions response = given().spec(request)
.queryParam("foo","bar")
.queryParam("foo","bar2")
.get("/api/v1/xxxx");
// then:
assertThat(response.statusCode()).isEqualTo(200);
assertThat(response.header("Authorization")).isEqualTo("foo secret bar");
// and:
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:
[source,json,indent=0]
----
{
"request" : {
"urlPath" : "/api/v1/xxxx",
"method" : "POST",
"headers" : {
"Authorization" : {
"equalTo" : "secret2"
}
},
"queryParameters" : {
"foo" : {
"equalTo" : "bar2"
}
},
"bodyPatterns" : [ {
"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]}}}\"}",
"headers" : {
"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:
[source,json,indent=0]
----
{
"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 or equal
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`: For a given parameter, find an object in the request body.
==== 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:
[source,groovy,indent=0]
----
include::{stubrunner_core_path}/src/test/resources/META-INF/spring.factories[indent=0]
----
The following is an example of a custom extension:
.TestWireMockExtensions.groovy
[source,groovy,indent=0]
----
include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/dsl/wiremock/TestWireMockExtensions.groovy[indent=0]
----
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.
[[contract-matchers]]
==== Dynamic Properties in the Matchers Sections
If you work with https://docs.pact.io/[Pact], the following discussion may seem familiar.
Quite a few users are used to having a separation between the body and setting the
dynamic parts of a contract.
You can use the `bodyMatchers` section for two reasons:
* Define the dynamic values that should end up in a stub.
You can set it in the `request` or `inputMessage` part of your contract.
* Verify the result of your test.
This section is present in the `response` or `outputMessage` side of the
contract.
Currently, Spring Cloud Contract Verifier supports only JSON Path-based matchers with the
following matching possibilities:
.Groovy DSL
* For the stubs:
** `byEquality()`: The value taken from the response via the provided JSON Path must be
equal to the value provided in the contract.
** `byRegex(...)`: The value taken from the response via the provided JSON Path must
match the regex.
** `byDate()`: The value taken from the response via the provided JSON Path must
match the regex for an ISO Date value.
** `byTimestamp()`: The value taken from the response via the provided JSON Path must
match the regex for an ISO DateTime value.
** `byTime()`: The value taken from the response via the provided JSON Path must
match the regex for an ISO Time value.
* For the verification:
** `byEquality()`: The value taken from the response via the provided JSON Path must be
equal to the provided value in the contract.
** `byRegex(...)`: The value taken from the response via the provided JSON Path must
match the regex.
** `byDate()`: The value taken from the response via the provided JSON Path must match
the regex for an ISO Date value.
** `byTimestamp()`: The value taken from the response via the provided JSON Path must
match the regex for an ISO DateTime value.
** `byTime()`: The value taken from the response via the provided JSON Path must match
the regex for an ISO Time value.
** `byType()`: The value taken from the response via the provided JSON Path needs to be
of the same type as the type defined in the body of the response in the contract.
`byType` can take a closure, in which you can set `minOccurrence` and `maxOccurrence`.
That way, you can assert the size of the flattened collection. To check the size of an
unflattened collection, use a custom method with the `byCommand(...)` testMatcher.
** `byCommand(...)`: The value taken from the response via the provided JSON Path is
passed as an input to the custom method that you provide. For example,
`byCommand('foo($it)')` results in calling a `foo` method to which the value matching the
JSON Path gets passed. The type of the object read from the JSON can be one of the
following, depending on the JSON path:
*** `String`: If you point to a `String` value.
*** `JSONArray`: If you point to a `List`.
*** `Map`: If you point to a `Map`.
*** `Number`: If you point to `Integer`, `Double`, or other kind of number.
*** `Boolean`: If you point to a `Boolean`.
** `byNull()`: The value taken from the response via the provided JSON Path must be null
.YAML
_Please read the Groovy section for detailed explanation of
what the types mean_
For YAML the structure of a matcher looks like this
[source,yml,indent=0]
----
- path: $.foo
type: by_regex
value: bar
----
Or if you want to use one of the predefined regular expressions
`[only_alpha_unicode, number, any_boolean, ip_address, hostname,
email, url, uuid, iso_date, iso_date_time, iso_time, iso_8601_with_offset, non_empty, non_blank]`:
[source,yml,indent=0]
----
- path: $.foo
type: by_regex
predefined: only_alpha_unicode
----
Below you can find the allowed list of `type`s.
* For `stubMatchers`:
** `by_equality`
** `by_regex`
** `by_date`
** `by_timestamp`
** `by_time`
* For `testMatchers`:
** `by_equality`
** `by_regex`
** `by_date`
** `by_timestamp`
** `by_time`
** `by_type`
*** there are 2 additional fields accepted: `minOccurrence` and `maxOccurrence`.
** `by_command`
** `by_null`
Consider the following example:
.Groovy DSL
[source,groovy,indent=0]
----
include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MockMvcMethodBodyBuilderWithMatchersSpec.groovy[tags=matchers,indent=0]
----
.YAML
[source,yml,indent=0]
----
include::{verifier_core_path}/src/test/resources/yml/contract_matchers.yml[indent=0]
----
In the preceding example, you can see the dynamic portions of the contract in the
`matchers` sections. For the request part, you can see that, for all fields but
`valueWithoutAMatcher`, the values of the regular expressions that the stub should
contain are explicitly set. For the `valueWithoutAMatcher`, the verification takes place
in the same way as without the use of matchers. In that case, the test performs an
equality check.
For the response side in the `bodyMatchers` section, we define the dynamic parts in a
similar manner. The only difference is that the `byType` matchers 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 or equal to the minimum occurrence.
* For `$.valueWithMax`, the engine checks the type and asserts whether the size is
smaller than or equal to the maximum occurrence.
* For `$.valueWithMinMax`, the engine checks the type and asserts whether the size is
between the min and maximum occurrence.
The resulting test would resemble the following example (note that an `and` section
separates the autogenerated assertions and the assertion from matchers):
[source,java,indent=0]
----
// given:
MockMvcRequestSpecification request = given()
.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\"}}");
// when:
ResponseOptions response = given().spec(request)
.get("/get");
// then:
assertThat(response.statusCode()).isEqualTo(200);
assertThat(response.header("Content-Type")).matches("application/json.*");
// and:
DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());
assertThatJson(parsedJson).field("['valueWithoutAMatcher']").isEqualTo("foo");
// and:
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 or be
statically imported to your tests. Notice that the `byCommand` call was converted to
`assertThatValueIsANumber(parsedJson.read("$.duck"));`. That means that the engine took
the method name and passed the proper JSON path as a parameter to it.
The resulting WireMock stub is in the following example:
[source,json,indent=0]
----
include::{plugins_path}/spring-cloud-contract-converters/src/test/groovy/org/springframework/cloud/contract/verifier/wiremock/DslToWireMockClientConverterSpec.groovy[tags=matchers,indent=0]
----
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 matchers for *all* the elements of the
collection.
Consider the following example:
[source,groovy,indent=0]
----
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'
]
]
)
bodyMatchers {
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 preceding code leads to creating the following test (the code block shows only the assertion section):
[source,java,indent=0]
----
and:
DocumentContext parsedJson = JsonPath.parse(response.body.asString())
assertThatJson(parsedJson).array("['events']").contains("['eventId']").isEqualTo("16f1ed75-0bcc-4f0d-a04d-3121798faf99")
assertThatJson(parsedJson).array("['events']").contains("['operation']").isEqualTo("EXPORT")
assertThatJson(parsedJson).array("['events']").contains("['operation']").isEqualTo("INPUT_PROCESSING")
assertThatJson(parsedJson).array("['events']").contains("['eventId']").isEqualTo("3bb4ac82-6652-462f-b6d1-75e424a0024a")
assertThatJson(parsedJson).array("['events']").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.
=== JAX-RS Support
The Spring Cloud Contract Verifier supports the JAX-RS 2 Client API. The base class needs
to define `protected WebTarget webTarget` and server initialization. The only option for
testing JAX-RS API is to start a web server. Also, a request with a body needs to have a
content type set. Otherwise, the default of `application/octet-stream` gets used.
In order to use JAX-RS mode, use the following settings:
[source,groovy,indent=0]
----
testMode == 'JAXRSCLIENT'
----
The following example shows a generated test API:
[source,groovy,indent=0]
----
include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/JaxRsClientMethodBuilderSpec.groovy[tags=jaxrs,indent=0]
----
=== Async Support
If you're using asynchronous communication on the server side (your controllers are
returning `Callable`, `DeferredResult`, and so on), then, inside your contract, you must
provide an `async()` method in the `response` section. The following code shows an example:
.Groovy DSL
[source,groovy,indent=0]
----
org.springframework.cloud.contract.spec.Contract.make {
request {
method GET()
url '/get'
}
response {
status OK()
body 'Passed'
async()
}
}
----
.YAML
[source,yml,indent=0]
----
response:
async: true
----
=== Working with Context Paths
Spring Cloud Contract supports context paths.
IMPORTANT: The only change needed to fully support context paths is the switch on the
*PRODUCER* side. Also, the autogenerated tests must use *EXPLICIT* mode. The consumer
side remains untouched. In order for the generated test to pass, you must use *EXPLICIT*
mode.
[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
.Maven
----
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<version>${spring-cloud-contract.version}</version>
<extensions>true</extensions>
<configuration>
<testMode>EXPLICIT</testMode>
</configuration>
</plugin>
----
[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"]
.Gradle
----
contracts {
testMode = 'EXPLICIT'
}
----
That way, you generate a test that *DOES NOT* use MockMvc. It means that you generate
real requests and you need to setup your generated test's base class to work on a real
socket.
Consider the following contract:
[source,groovy,indent=0]
----
include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/SingleTestGeneratorSpec.groovy[tags=context_path_contract,indent=0]
----
The following example shows how to set up a base class and Rest Assured:
[source,groovy,indent=0]
----
include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/SingleTestGeneratorSpec.groovy[tags=context_path_baseclass,indent=0]
----
If you do it this way:
* All of your requests in the autogenerated tests are sent to the real endpoint with your
context path included (for example, `/my-context-path/url`).
* Your contracts reflect that you have a context path. Your generated stubs also have
that information (for example, in the stubs, you have to call `/my-context-path/url`).
=== Messaging Top-Level Elements
The DSL for messaging looks a little bit different than the one that focuses on HTTP. The
following sections explain the differences:
* <<contract-dsl-output-triggered-method>>
* <<contract-dsl-output-triggered-message>>
* <<contract-dsl-consumer-producer>>
* <<contract-dsl-common>>
[[contract-dsl-output-triggered-method]]
==== Output Triggered by a Method
The output message can be triggered by calling a method (such as a `Scheduler` when a was
started and a message was sent), as shown in the following example:
.Groovy DSL
[source,groovy]
----
include::{tests_path}/samples-messaging-integration/src/test/groovy/com/example/IntegrationMessagingApplicationSpec.groovy[tags=method_trigger,indent=0]
----
.YAML
[source,yml,indent=0]
----
include::{verifier_core_path}/src/test/resources/yml/contract_message_method.yml[indent=0]
----
In the previous example case, the output message is sent to `output` if a method called
`bookReturnedTriggered` is executed. On the message *publisher's* side, we generate a
test that calls that method to trigger the message. On the *consumer* side, you can use
the `some_label` to trigger the message.
[[contract-dsl-output-triggered-message]]
==== Output Triggered by a Message
The output message can be triggered by receiving a message, as shown in the following
example:
.Groovy DSL
[source,groovy]
----
include::{tests_path}/samples-messaging-integration/src/test/groovy/com/example/IntegrationMessagingApplicationSpec.groovy[tags=message_trigger,indent=0]
----
.YAML
[source,yml,indent=0]
----
include::{verifier_core_path}/src/test/resources/yml/contract_message_input_message.yml[indent=0]
----
In the preceding example, the output message is sent to `output` if a proper message is
received on the `input` destination. On the message *publisher's* side, the engine
generates a test that sends the input message to the defined destination. On the
*consumer* side, you can either send a message to the input destination or use a label
(`some_label` in the example) to trigger the message.
[[contract-dsl-consumer-producer]]
==== Consumer/Producer
IMPORTANT: This section is valid only for Groovy DSL.
In HTTP, you have a notion of `client`/`stub and `server`/`test` notation. You can also
use those paradigms in messaging. In addition, Spring Cloud Contract Verifier also
provides the `consumer` and `producer` methods, as presented in the following example
(note that you can use either `$` or `value` methods to provide `consumer` and `producer`
parts):
[source,groovy]
----
include::{verifier_core_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[tags=consumer_producer]
----
[[contract-dsl-common]]
==== Common
In the `input` or `outputMessage` section you can call `assertThat` with the name
of a `method` (e.g. `assertThatMessageIsOnTheQueue()`) that you have defined in the
base class or in a static import. Spring Cloud Contract will execute that method
in the generated test.
=== Multiple Contracts in One File
You can define multiple contracts in one file. Such a contract might resemble the
following example:
.Groovy DSL
[source,groovy,indent=0]
----
include::{plugins_path}/spring-cloud-contract-maven-plugin/src/test/projects/multiple-contracts/src/test/resources/contracts/com/hello/v1/WithList.groovy[lines=18..-1,indent=0]
----
.YAML
[source,yml,indent=0]
----
include::{verifier_core_path}/src/test/resources/yml/multiple_contracts.yml[indent=0]
----
In the preceding example, one contract has the `name` field and the other does not. This
leads to generation of two tests that look more or less like this:
[source,java,indent=0]
----
package org.springframework.cloud.contract.verifier.tests.com.hello;
import com.example.TestBase;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import com.jayway.restassured.module.mockmvc.specification.MockMvcRequestSpecification;
import com.jayway.restassured.response.ResponseOptions;
import org.junit.Test;
import static com.jayway.restassured.module.mockmvc.RestAssuredMockMvc.*;
import static com.toomuchcoding.jsonassert.JsonAssertion.assertThatJson;
import static org.assertj.core.api.Assertions.assertThat;
public class V1Test extends TestBase {
@Test
public void validate_should_post_a_user() throws Exception {
// given:
MockMvcRequestSpecification request = given();
// when:
ResponseOptions response = given().spec(request)
.post("/users/1");
// then:
assertThat(response.statusCode()).isEqualTo(200);
}
@Test
public void validate_withList_1() throws Exception {
// given:
MockMvcRequestSpecification request = given();
// when:
ResponseOptions response = given().spec(request)
.post("/users/2");
// then:
assertThat(response.statusCode()).isEqualTo(200);
}
}
----
Notice that, for the contract that has the `name` field, the generated test method is named
`validate_should_post_a_user`. For the one that does not have the name, it is called
`validate_withList_1`. It corresponds to the name of the file `WithList.groovy` and the
index of the contract in the list.
The generated stubs is shown in the following example:
[source]
----
should post a user.json
1_WithList.json
----
As you can see, the first file got the `name` parameter from the contract. The second
got the name of the contract file (`WithList.groovy`) prefixed with the index (in this
case, the contract had an index of `1` in the list of contracts in the file).
TIP: As you can see, it iss much better if you name your contracts because doing so makes
your tests far more meaningful.
== Customization
IMPORTANT: This section is valid only for Groovy DSL
You can customize the Spring Cloud Contract Verifier by extending the DSL, as shown in
the remainder of this section.
=== Extending the DSL
You can provide your own functions to the DSL. The key requirement for this feature is to
maintain the static compatibility. Later in this document, you can see examples of:
* Creating a JAR with reusable classes.
* Referencing of these classes in the DSLs.
You can find the full example
https://github.com/spring-cloud-samples/spring-cloud-contract-samples[here].
==== Common JAR
The following examples show three classes that can be reused in the DSLs.
*PatternUtils* contains functions used by both the **consumer** and the **producer**.
[source,java]
----
include::{samples_url}/common/src/main/java/com/example/PatternUtils.java[]
----
*ConsumerUtils* contains functions used by the **consumer**.
[source,java]
----
include::{samples_url}/common/src/main/java/com/example/ConsumerUtils.java[]
----
*ProducerUtils* contains functions used by the **producer**.
[source,java]
----
include::{samples_url}/common/src/main/java/com/example/ProducerUtils.java[]
----
==== Adding the Dependency to the Project
In order for the plugins and IDE to be able to reference the common JAR classes, you need
to pass the dependency to your project.
// TODO missing code block here - we should show an example adding a dependency
==== Test the Dependency in the Project's Dependencies
First, add the common jar dependency as a test dependency. Because your contracts files
are available on the test resources path, the common jar classes automatically become
visible in your Groovy files. The following examples show how to test the dependency:
[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
.Maven
----
include::{samples_url}/producer/pom.xml[tags=test_dep,indent=0]
----
[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"]
.Gradle
----
include::{samples_url}/producer/build.gradle[tags=test_dep,indent=0]
----
==== Test a Dependency in the Plugin's Dependencies
Now, you must add the dependency for the plugin to reuse at runtime, as shown in the
following example:
[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
.Maven
----
include::{samples_url}/producer/pom.xml[tags=test_dep_in_plugin,indent=0]
----
[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"]
.Gradle
----
include::{samples_url}/producer/build.gradle[tags=test_dep_in_plugin,indent=0]
----
==== Referencing classes in DSLs
You can now reference your classes in your DSL, as shown in the following example:
[source,groovy]
----
include::{samples_url}/producer/src/test/resources/contracts/beer/rest/shouldGrantABeerIfOldEnough.groovy[indent=0]
----
== Using the Pluggable Architecture
You may encounter cases where you have your contracts have been defined in other formats,
such as YAML, RAML or PACT. In those cases, you still want to benefit from the automatic
generation of tests and stubs. You can add your own implementation for generating both
tests and stubs. Also, you can customize the way tests are generated (for example, you
can generate tests for other languages) and the way stubs are generated (for example, you
can generate stubs for other HTTP server implementations).
=== Custom Contract Converter
The `ContractConverter` interface lets you register your own implementation of a contract
structure converter. The following code listing shows the `ContractConverter` interface:
[source,groovy]
----
include::{contract_spec_path}/src/main/groovy/org/springframework/cloud/contract/spec/ContractConverter.groovy[indent=0,lines=17..-1]
----
Your implementation must define the condition on which it should start the
conversion. Also, you must define how to perform that conversion in both directions.
IMPORTANT: Once you create your implementation, you must create a
`/META-INF/spring.factories` file in which you provide the fully qualified name of your
implementation.
The following example shows a typical `spring.factories` file:
[source]
----
org.springframework.cloud.contract.spec.ContractConverter=\
org.springframework.cloud.contract.verifier.converter.YamlContractConverter
----
[[pact-converter]]
==== Pact Converter
Spring Cloud Contract includes support for https://docs.pact.io/[Pact] representation of
contracts up until v4. Instead of using the Groovy DSL, you can use Pact files. In this section, we
present how to add Pact support for your project. Note however that not all functionality is supported.
Starting with v3 you can combine multiple matcher for the same element;
you can use matchers for the body, headers, request and path; and you can use value generators.
Spring Cloud Contract currently only supports multiple matchers that are combined using the AND rule logic.
Next to that the request and path matchers are skipped during the conversion.
When using a date, time or datetime value generator with a given format,
the given format will be skipped and the ISO format will be used.
==== Pact Contract
Consider following example of a Pact contract, which is a file under the
`src/test/resources/contracts` folder.
[source,javascript,indent=0]
----
include::{standalone_pact_path}/pact-http-server/src/test/resources/contracts/fraud/shouldMarkClientAsFraud.json[indent=0]
----
The remainder of this section about using Pact refers to the preceding file.
==== Pact for Producers
On the producer side, you must add two additional dependencies to your plugin
configuration. One is the Spring Cloud Contract Pact support, and the other represents
the current Pact version that you use.
[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
.Maven
----
include::{standalone_pact_path}/pact-http-server/pom.xml[tags=pact_dependency,indent=0]
----
[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"]
.Gradle
----
include::{standalone_pact_path}/pact-http-server/build.gradle[tags=pact_dependency,indent=0]
----
When you execute the build of your application, a test will be generated. The generated
test might be as follows:
[source,java,indent=0]
----
@Test
public void validate_shouldMarkClientAsFraud() throws Exception {
// given:
MockMvcRequestSpecification request = given()
.header("Content-Type", "application/vnd.fraud.v1+json")
.body("{\"clientId\":\"1234567890\",\"loanAmount\":99999}");
// when:
ResponseOptions response = given().spec(request)
.put("/fraudcheck");
// then:
assertThat(response.statusCode()).isEqualTo(200);
assertThat(response.header("Content-Type")).matches("application/vnd\\.fraud\\.v1\\+json.*");
// and:
DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());
assertThatJson(parsedJson).field("['rejectionReason']").isEqualTo("Amount too high");
// and:
assertThat(parsedJson.read("$.fraudCheckStatus", String.class)).matches("FRAUD");
}
----
The corresponding generated stub might be as follows:
[source,javascript,indent=0]
----
{
"id" : "996ae5ae-6834-4db6-8fac-358ca187ab62",
"uuid" : "996ae5ae-6834-4db6-8fac-358ca187ab62",
"request" : {
"url" : "/fraudcheck",
"method" : "PUT",
"headers" : {
"Content-Type" : {
"matches" : "application/vnd\\.fraud\\.v1\\+json.*"
}
},
"bodyPatterns" : [ {
"matchesJsonPath" : "$[?(@.['loanAmount'] == 99999)]"
}, {
"matchesJsonPath" : "$[?(@.clientId =~ /([0-9]{10})/)]"
} ]
},
"response" : {
"status" : 200,
"body" : "{\"fraudCheckStatus\":\"FRAUD\",\"rejectionReason\":\"Amount too high\"}",
"headers" : {
"Content-Type" : "application/vnd.fraud.v1+json;charset=UTF-8"
},
"transformers" : [ "response-template" ]
},
}
----
==== Pact for Consumers
On the producer side, you must add two additional dependencies to your project
dependencies. One is the Spring Cloud Contract Pact support, and the other represents the
current Pact version that you use.
[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
.Maven
----
include::{standalone_pact_path}/pact-http-client/pom.xml[tags=pact_dependency,indent=0]
----
[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"]
.Gradle
----
include::{standalone_pact_path}/pact-http-client/build.gradle[tags=pact_dependency,indent=0]
----
=== Using the Custom Test Generator
If you want to generate tests for languages other than Java or you are not happy with the
way the verifier builds Java tests, you can register your own implementation.
The `SingleTestGenerator` interface lets you register your own implementation. The
following code listing shows the `SingleTestGenerator` interface:
[source,groovy]
----
include::{verifier_core_path}/src/main/groovy/org/springframework/cloud/contract/verifier/builder/SingleTestGenerator.groovy[indent=0,lines=17..-1]
----
Again, you must provide a `spring.factories` file, such as the one shown in the following
example:
[source]
----
org.springframework.cloud.contract.verifier.builder.SingleTestGenerator=/
com.example.MyGenerator
----
=== Using the Custom Stub Generator
If you want to generate stubs for stub servers other than WireMock, you can plug in your
own implementation of the `StubGenerator` interface. The following code listing shows the
`StubGenerator` interface:
[source,groovy]
----
include::{converters_path}/src/main/groovy/org/springframework/cloud/contract/verifier/converter/StubGenerator.groovy[indent=0,lines=16..-1]
----
Again, you must provide a `spring.factories` file, such as the one shown in the following
example:
[source]
----
include::{converters_path}/src/main/resources/META-INF/spring.factories[indent=0]
----
The default implementation is the WireMock stub generation.
TIP: You can provide multiple stub generator implementations. For example, from a single
DSL, you can produce both WireMock stubs and Pact files.
=== Using the Custom Stub Runner
If you decide to use a custom stub generation, you also need a custom way of running
stubs with your different stub provider.
Assume that you use https://github.com/dreamhead/moco[Moco] to build your stubs and that
you have written a stub generator and placed your stubs in a JAR file.
In order for Stub Runner to know how to run your stubs, you have to define a custom
HTTP Stub server implementation, which might resemble the following example:
[source,groovy]
----
include::{tests_path}/spring-cloud-contract-stub-runner-moco/src/test/groovy/org/springframework/cloud/contract/stubrunner/provider/moco/MocoHttpServerStub.groovy[indent=0,lines=16..-1]
----
Then, you can register it in your `spring.factories` file, as shown in the following
example:
[source]
----
org.springframework.cloud.contract.stubrunner.HttpServerStub=\
org.springframework.cloud.contract.stubrunner.provider.moco.MocoHttpServerStub
----
Now you can run stubs with Moco.
IMPORTANT: If you do not provide any implementation, then the default (WireMock)
implementation is used. If you provide more than one, the first one on the list is used.
=== Using the Custom Stub Downloader
You can customize the way your stubs are downloaded by creating an implementation of the
`StubDownloaderBuilder` interface, as shown in the following example:
[source,java]
----
package com.example;
class CustomStubDownloaderBuilder implements StubDownloaderBuilder {
@Override
public StubDownloader build(final StubRunnerOptions stubRunnerOptions) {
return new StubDownloader() {
@Override
public Map.Entry<StubConfiguration, File> downloadAndUnpackStubJar(
StubConfiguration config) {
File unpackedStubs = retrieveStubs();
return new AbstractMap.SimpleEntry<>(
new StubConfiguration(config.getGroupId(), config.getArtifactId(), version,
config.getClassifier()), unpackedStubs);
}
File retrieveStubs() {
// here goes your custom logic to provide a folder where all the stubs reside
}
}
----
Then you can register it in your `spring.factories` file, as shown in the following
example:
[source]
----
# Example of a custom Stub Downloader Provider
org.springframework.cloud.contract.stubrunner.StubDownloaderBuilder=\
com.example.CustomStubDownloaderBuilder
----
Now you can pick a folder with the source of your stubs.
IMPORTANT: If you do not provide any implementation, then the default is used (scan classpath).
If you provide the `stubsMode = StubRunnerProperties.StubsMode.LOCAL` or
`, stubsMode = StubRunnerProperties.StubsMode.REMOTE` then the Aether implementation will be used
If you provide more than one, then the first one on the list is used.
[[scm-stub-downloader]]
=== Using the SCM Stub Downloader
Whenever the `repositoryRoot` starts with a SCM protocol
(currently we support only `git://`), the stub downloader will try
to clone the repository and use it as a source of contracts
to generate tests or stubs.
Either via environment variables, system properties, properties set
inside the plugin or contracts repository configuration you can
tweak the downloader's behaviour. Below you can find the list of
properties
.SCM Stub Downloader properties
|===
|Type of a property |Name of the property | Description
|
* `git.branch` (plugin prop)
* `stubrunner.properties.git.branch` (system prop)
* `STUBRUNNER_PROPERTIES_GIT_BRANCH` (env prop)
|master
|Which branch to checkout
|
* `git.username` (plugin prop)
* `stubrunner.properties.git.username` (system prop)
* `STUBRUNNER_PROPERTIES_GIT_USERNAME` (env prop)
|
|Git clone username
|
* `git.password` (plugin prop)
* `stubrunner.properties.git.password` (system prop)
* `STUBRUNNER_PROPERTIES_GIT_PASSWORD` (env prop)
|
|Git clone password
|
* `git.no-of-attempts` (plugin prop)
* `stubrunner.properties.git.no-of-attempts` (system prop)
* `STUBRUNNER_PROPERTIES_GIT_NO_OF_ATTEMPTS` (env prop)
|10
|Number of attempts to push the commits to `origin`
|
* `git.wait-between-attempts` (Plugin prop)
* `stubrunner.properties.git.wait-between-attempts` (system prop)
* `STUBRUNNER_PROPERTIES_GIT_WAIT_BETWEEN_ATTEMPTS` (env prop)
|1000
|Number of millis to wait between attempts to push the commits to `origin`
|===
[[pact-stub-downloader]]
=== Using the Pact Stub Downloader
Whenever the `repositoryRoot` starts with a Pact protocol
(starts with `pact://`), the stub downloader will try
to fetch the Pact contract definitions from the Pact Broker.
Whatever is set after `pact://` will be parsed as the Pact Broker URL.
Either via environment variables, system properties, properties set
inside the plugin or contracts repository configuration you can
tweak the downloader's behaviour. Below you can find the list of
properties
.SCM Stub Downloader properties
|===
|Type of a property |Name of the property | Description
|
* `pactbroker.host` (plugin prop)
* `stubrunner.properties.pactbroker.host` (system prop)
* `STUBRUNNER_PROPERTIES_PACTBROKER_HOST` (env prop)
|Host from URL passed to `repositoryRoot`
|What is the URL of Pact Broker
|
* `pactbroker.port` (plugin prop)
* `stubrunner.properties.pactbroker.port` (system prop)
* `STUBRUNNER_PROPERTIES_PACTBROKER_PORT` (env prop)
|Port from URL passed to `repositoryRoot`
|What is the port of Pact Broker
|
* `pactbroker.protocol` (plugin prop)
* `stubrunner.properties.pactbroker.protocol` (system prop)
* `STUBRUNNER_PROPERTIES_PACTBROKER_PROTOCOL` (env prop)
|Protocol from URL passed to `repositoryRoot`
|What is the protocol of Pact Broker
|
* `pactbroker.tags` (plugin prop)
* `stubrunner.properties.pactbroker.tags` (system prop)
* `STUBRUNNER_PROPERTIES_PACTBROKER_TAGS` (env prop)
|Version of the stub, or `latest` if version is `+`
|What tags should be used to fetch the stub
|
* `pactbroker.auth.scheme` (plugin prop)
* `stubrunner.properties.pactbroker.auth.scheme` (system prop)
* `STUBRUNNER_PROPERTIES_PACTBROKER_AUTH_SCHEME` (env prop)
|`Basic`
|What kind of authentication should be used to connect to the Pact Broker
|
* `pactbroker.auth.username` (plugin prop)
* `stubrunner.properties.pactbroker.auth.username` (system prop)
* `STUBRUNNER_PROPERTIES_PACTBROKER_AUTH_USERNAME` (env prop)
|
|Username used to connect to the Pact Broker
|
* `pactbroker.auth.password` (plugin prop)
* `stubrunner.properties.pactbroker.auth.password` (system prop)
* `STUBRUNNER_PROPERTIES_PACTBROKER_AUTH_PASSWORD` (env prop)
|
|Password used to connect to the Pact Broker
|
* `pactbroker.provider-name-with-group-id` (plugin prop)
* `stubrunner.properties.pactbroker.provider-name-with-group-id` (system prop)
* `STUBRUNNER_PROPERTIES_PACTBROKER_PROVIDER_NAME_WITH_GROUP_ID` (env prop)
|false
|When `true`, the provider name will be a combination of `groupId:artifactId`. If `false`, just `artifactId` is used
|===