2208 lines
205 KiB
HTML
2208 lines
205 KiB
HTML
<html><head>
|
|
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
|
<title>94. Contract DSL</title><link rel="stylesheet" type="text/css" href="css/manual-multipage.css"><meta name="generator" content="DocBook XSL Stylesheets V1.79.1"><link rel="home" href="multi_spring-cloud.html" title="Spring Cloud"><link rel="up" href="multi__spring_cloud_contract.html" title="Part XIII. Spring Cloud Contract"><link rel="prev" href="multi_stub-runner-for-messaging.html" title="93. Stub Runner for Messaging"><link rel="next" href="multi__customization.html" title="95. Customization"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">94. Contract DSL</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="multi_stub-runner-for-messaging.html">Prev</a> </td><th width="60%" align="center">Part XIII. Spring Cloud Contract</th><td width="20%" align="right"> <a accesskey="n" href="multi__customization.html">Next</a></td></tr></table><hr></div><div class="chapter"><div class="titlepage"><div><div><h2 class="title"><a name="contract-dsl" href="#contract-dsl"></a>94. Contract DSL</h2></div></div></div><p>Spring Cloud Contract supports out of the box 2 types of DSL. One written in
|
|
<code class="literal">Groovy</code> and one written in <code class="literal">YAML</code>.</p><p>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.</p><div class="important" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Important"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Important]" src="images/important.png"></td><th align="left">Important</th></tr><tr><td align="left" valign="top"><p>Remember that, inside the Groovy contract file, you have to provide the fully
|
|
qualified name to the <code class="literal">Contract</code> class and <code class="literal">make</code> static imports, such as
|
|
<code class="literal">org.springframework.cloud.spec.Contract.make { …​ }</code>. You can also provide an import to
|
|
the <code class="literal">Contract</code> class: <code class="literal">import org.springframework.cloud.spec.Contract</code> and then call
|
|
<code class="literal">Contract.make { …​ }</code>.</p></td></tr></table></div><div class="tip" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Tip"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Tip]" src="images/tip.png"></td><th align="left">Tip</th></tr><tr><td align="left" valign="top"><p>Spring Cloud Contract supports defining multiple contracts in a single file.</p></td></tr></table></div><p>The following is a complete example of a Groovy contract definition:</p><pre class="programlisting"></pre><p>The following is a complete example of a YAML contract definition:</p><pre class="programlisting">description: Some description
|
|
name: some name
|
|
priority: 8
|
|
ignored: true
|
|
request:
|
|
url: /foo
|
|
queryParameters:
|
|
a: b
|
|
b: c
|
|
method: PUT
|
|
headers:
|
|
foo: bar
|
|
fooReq: baz
|
|
body:
|
|
foo: bar
|
|
matchers:
|
|
body:
|
|
- path: $.foo
|
|
type: by_regex
|
|
value: bar
|
|
headers:
|
|
- key: foo
|
|
regex: bar
|
|
response:
|
|
status: 200
|
|
headers:
|
|
foo2: bar
|
|
foo3: foo33
|
|
fooRes: baz
|
|
body:
|
|
foo2: bar
|
|
foo3: baz
|
|
nullValue: null
|
|
matchers:
|
|
body:
|
|
- path: $.foo2
|
|
type: by_regex
|
|
value: bar
|
|
- path: $.foo3
|
|
type: by_command
|
|
value: executeMe($it)
|
|
- path: $.nullValue
|
|
type: by_null
|
|
value: null
|
|
headers:
|
|
- key: foo2
|
|
regex: bar
|
|
- key: foo3
|
|
command: andMeToo($it)</pre><div class="tip" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Tip"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Tip]" src="images/tip.png"></td><th align="left">Tip</th></tr><tr><td align="left" valign="top"><p>You can compile contracts to stubs mapping using standalone maven command:
|
|
<code class="literal">mvn org.springframework.cloud:spring-cloud-contract-maven-plugin:convert</code></p></td></tr></table></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_limitations" href="#_limitations"></a>94.1 Limitations</h2></div></div></div><div class="warning" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Warning"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Warning]" src="images/warning.png"></td><th align="left">Warning</th></tr><tr><td align="left" valign="top"><p>Spring Cloud Contract Verifier does not properly support XML. Please use JSON or
|
|
help us implement this feature.</p></td></tr></table></div><div class="warning" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Warning"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Warning]" src="images/warning.png"></td><th align="left">Warning</th></tr><tr><td align="left" valign="top"><p>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 <code class="literal">true</code>:
|
|
<code class="literal">spring.cloud.contract.verifier.assert.size</code>. By default, this feature is set to <code class="literal">false</code>.
|
|
You can also provide the <code class="literal">assertJsonSize</code> property in the plugin configuration.</p></td></tr></table></div><div class="warning" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Warning"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Warning]" src="images/warning.png"></td><th align="left">Warning</th></tr><tr><td align="left" valign="top"><p>Because JSON structure can have any form, it can be impossible to parse it
|
|
properly when using the Groovy DSL and the <code class="literal">value(consumer(…​), producer(…​))</code> notation in <code class="literal">GString</code>. That
|
|
is why you should use the Groovy Map notation.</p></td></tr></table></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_common_top_level_elements" href="#_common_top_level_elements"></a>94.2 Common Top-Level elements</h2></div></div></div><p>The following sections describe the most common top-level elements:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><a class="xref" href="multi_contract-dsl.html#contract-dsl-description" title="94.2.1 Description">Section 94.2.1, “Description”</a></li><li class="listitem"><a class="xref" href="multi_contract-dsl.html#contract-dsl-name" title="94.2.2 Name">Section 94.2.2, “Name”</a></li><li class="listitem"><a class="xref" href="multi_contract-dsl.html#contract-dsl-ignoring-contracts" title="94.2.3 Ignoring Contracts">Section 94.2.3, “Ignoring Contracts”</a></li><li class="listitem"><a class="xref" href="multi_contract-dsl.html#contract-dsl-passing-values-from-files" title="94.2.4 Passing Values from Files">Section 94.2.4, “Passing Values from Files”</a></li><li class="listitem"><a class="xref" href="multi_contract-dsl.html#contract-dsl-http-top-level-elements" title="94.2.5 HTTP Top-Level Elements">Section 94.2.5, “HTTP Top-Level Elements”</a></li></ul></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="contract-dsl-description" href="#contract-dsl-description"></a>94.2.1 Description</h3></div></div></div><p>You can add a <code class="literal">description</code> to your contract. The description is arbitrary text. The
|
|
following code shows an example:</p><p><b>Groovy DSL. </b>
|
|
</p><pre class="programlisting"> org.springframework.cloud.contract.spec.Contract.make {
|
|
description(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">''</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'
|
|
</span>given:
|
|
An input
|
|
when:
|
|
Sth happens
|
|
then:
|
|
Output
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">''</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">')
|
|
</span> }</pre><p>
|
|
</p><p><b>YAML. </b>
|
|
</p><pre class="programlisting">description: Some description
|
|
name: some name
|
|
priority: 8
|
|
ignored: true
|
|
request:
|
|
url: /foo
|
|
queryParameters:
|
|
a: b
|
|
b: c
|
|
method: PUT
|
|
headers:
|
|
foo: bar
|
|
fooReq: baz
|
|
body:
|
|
foo: bar
|
|
matchers:
|
|
body:
|
|
- path: $.foo
|
|
type: by_regex
|
|
value: bar
|
|
headers:
|
|
- key: foo
|
|
regex: bar
|
|
response:
|
|
status: 200
|
|
headers:
|
|
foo2: bar
|
|
foo3: foo33
|
|
fooRes: baz
|
|
body:
|
|
foo2: bar
|
|
foo3: baz
|
|
nullValue: null
|
|
matchers:
|
|
body:
|
|
- path: $.foo2
|
|
type: by_regex
|
|
value: bar
|
|
- path: $.foo3
|
|
type: by_command
|
|
value: executeMe($it)
|
|
- path: $.nullValue
|
|
type: by_null
|
|
value: null
|
|
headers:
|
|
- key: foo2
|
|
regex: bar
|
|
- key: foo3
|
|
command: andMeToo($it)</pre><p>
|
|
</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="contract-dsl-name" href="#contract-dsl-name"></a>94.2.2 Name</h3></div></div></div><p>You can provide a name for your contract. Assume that you provided the following name:
|
|
<code class="literal">should register a user</code>. If you do so, the name of the autogenerated test is
|
|
<code class="literal">validate_should_register_a_user</code>. Also, the name of the stub in a WireMock stub is
|
|
<code class="literal">should_register_a_user.json</code>.</p><div class="important" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Important"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Important]" src="images/important.png"></td><th align="left">Important</th></tr><tr><td align="left" valign="top"><p>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.</p></td></tr></table></div><p><b>Groovy DSL. </b>
|
|
</p><pre class="programlisting">org.springframework.cloud.contract.spec.Contract.make {
|
|
name(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"some_special_name"</span>)
|
|
}</pre><p>
|
|
</p><p><b>YAML. </b>
|
|
</p><pre class="programlisting">name: some name</pre><p>
|
|
</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="contract-dsl-ignoring-contracts" href="#contract-dsl-ignoring-contracts"></a>94.2.3 Ignoring Contracts</h3></div></div></div><p>If you want to ignore a contract, you can either set a value of ignored contracts in the
|
|
plugin configuration or set the <code class="literal">ignored</code> property on the contract itself:</p><p><b>Groovy DSL. </b>
|
|
</p><pre class="programlisting">org.springframework.cloud.contract.spec.Contract.make {
|
|
ignored()
|
|
}</pre><p>
|
|
</p><p><b>YAML. </b>
|
|
</p><pre class="programlisting">ignored: true</pre><p>
|
|
</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="contract-dsl-passing-values-from-files" href="#contract-dsl-passing-values-from-files"></a>94.2.4 Passing Values from Files</h3></div></div></div><p>Starting with version <code class="literal">1.2.0</code>, you can pass values from files. Assume that you have the
|
|
following resources in our project.</p><pre class="programlisting">└── src
|
|
└── <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">test</span>
|
|
└── resources
|
|
└── contracts
|
|
├── readFromFile.groovy
|
|
├── request.json
|
|
└── response.json</pre><p>Further assume that your contract is as follows:</p><p><b>Groovy DSL. </b>
|
|
</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">/*
|
|
* Copyright 2013-2019 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
|
|
*
|
|
* https://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.
|
|
*/</span>
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> org.springframework.cloud.contract.spec.Contract
|
|
|
|
Contract.make {
|
|
request {
|
|
method(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'PUT'</span>)
|
|
headers {
|
|
contentType(applicationJson())
|
|
}
|
|
body(file(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"request.json"</span>))
|
|
url(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"/1"</span>)
|
|
}
|
|
response {
|
|
status OK()
|
|
body(file(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"response.json"</span>))
|
|
headers {
|
|
contentType(applicationJson())
|
|
}
|
|
}
|
|
}</pre><p>
|
|
</p><p><b>YAML. </b>
|
|
</p><pre class="programlisting">request:
|
|
method: GET
|
|
url: /foo
|
|
bodyFromFile: request.json
|
|
response:
|
|
status: 200
|
|
bodyFromFile: response.json</pre><p>
|
|
</p><p>Further assume that the JSON files is as follows:</p><p><span class="strong"><strong>request.json</strong></span></p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"status"</span>: <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"REQUEST"</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">}</span></pre><p><span class="strong"><strong>response.json</strong></span></p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"status"</span>: <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"RESPONSE"</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">}</span></pre><p>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.</p><p>If you need to pass the contents of a file in a binary form
|
|
it’s enough for you to use the <code class="literal">fileAsBytes</code> method in Groovy DSL or <code class="literal">bodyFromFileAsBytes</code> field in YAML.</p><p><b>Groovy DSL. </b>
|
|
</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> org.springframework.cloud.contract.spec.Contract
|
|
|
|
Contract.make {
|
|
request {
|
|
url(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"/1"</span>)
|
|
method(PUT())
|
|
headers {
|
|
contentType(applicationOctetStream())
|
|
}
|
|
body(fileAsBytes(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"request.pdf"</span>))
|
|
}
|
|
response {
|
|
status <span class="hl-number">200</span>
|
|
body(fileAsBytes(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"response.pdf"</span>))
|
|
headers {
|
|
contentType(applicationOctetStream())
|
|
}
|
|
}
|
|
}</pre><p>
|
|
</p><p><b>YAML. </b>
|
|
</p><pre class="programlisting">request:
|
|
url: /1
|
|
method: PUT
|
|
headers:
|
|
Content-Type: application/octet-stream
|
|
bodyFromFileAsBytes: request.pdf
|
|
response:
|
|
status: 200
|
|
bodyFromFileAsBytes: response.pdf
|
|
headers:
|
|
Content-Type: application/octet-stream</pre><p>
|
|
</p><div class="important" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Important"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Important]" src="images/important.png"></td><th align="left">Important</th></tr><tr><td align="left" valign="top"><p>You should use this approach whenever you want to work with binary payloads both for HTTP and messaging.</p></td></tr></table></div></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="contract-dsl-http-top-level-elements" href="#contract-dsl-http-top-level-elements"></a>94.2.5 HTTP Top-Level Elements</h3></div></div></div><p>The following methods can be called in the top-level closure of a contract definition.
|
|
<code class="literal">request</code> and <code class="literal">response</code> are mandatory. <code class="literal">priority</code> is optional.</p><p><b>Groovy DSL. </b>
|
|
</p><pre class="programlisting">org.springframework.cloud.contract.spec.Contract.make {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// Definition of HTTP request part of the contract</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// (this can be a valid request or invalid depending</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// on type of contract being specified).</span>
|
|
request {
|
|
method GET()
|
|
url <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"/foo"</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">//...</span>
|
|
}
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// Definition of HTTP response part of the contract</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// (a service implementing this contract should respond</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// with following response after receiving request</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// specified in "request" part above).</span>
|
|
response {
|
|
status <span class="hl-number">200</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">//...</span>
|
|
}
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// Contract priority, which can be used for overriding</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// contracts (1 is highest). Priority is optional.</span>
|
|
priority <span class="hl-number">1</span>
|
|
}</pre><p>
|
|
</p><p><b>YAML. </b>
|
|
</p><pre class="programlisting">priority: 8
|
|
request:
|
|
...
|
|
response:
|
|
...</pre><p>
|
|
</p><div class="important" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Important"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Important]" src="images/important.png"></td><th align="left">Important</th></tr><tr><td align="left" valign="top"><p>If you want to make your contract have a <span class="strong"><strong>higher</strong></span> value of priority
|
|
you need to pass a <span class="strong"><strong>lower</strong></span> number to the <code class="literal">priority</code> tag / method. E.g. <code class="literal">priority</code> with
|
|
value <code class="literal">5</code> has <span class="strong"><strong>higher</strong></span> priority than <code class="literal">priority</code> with value <code class="literal">10</code>.</p></td></tr></table></div></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_request" href="#_request"></a>94.3 Request</h2></div></div></div><p>The HTTP protocol requires only <span class="strong"><strong>method and url</strong></span> to be specified in a request. The
|
|
same information is mandatory in request definition of the Contract.</p><p><b>Groovy DSL. </b>
|
|
</p><pre class="programlisting">org.springframework.cloud.contract.spec.Contract.make {
|
|
request {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// HTTP request method (GET/POST/PUT/DELETE).</span>
|
|
method <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'GET'</span>
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// Path component of request URL is specified as follows.</span>
|
|
urlPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'/users'</span>)
|
|
}
|
|
|
|
response {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">//...</span>
|
|
status <span class="hl-number">200</span>
|
|
}
|
|
}</pre><p>
|
|
</p><p><b>YAML. </b>
|
|
</p><pre class="programlisting">method: PUT
|
|
url: /foo</pre><p>
|
|
</p><p>It is possible to specify an absolute rather than relative <code class="literal">url</code>, but using <code class="literal">urlPath</code> is
|
|
the recommended way, as doing so makes the tests <span class="strong"><strong>host-independent</strong></span>.</p><p><b>Groovy DSL. </b>
|
|
</p><pre class="programlisting">org.springframework.cloud.contract.spec.Contract.make {
|
|
request {
|
|
method <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'GET'</span>
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// Specifying `url` and `urlPath` in one contract is illegal.</span>
|
|
url(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'http://localhost:8888/users'</span>)
|
|
}
|
|
|
|
response {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">//...</span>
|
|
status <span class="hl-number">200</span>
|
|
}
|
|
}</pre><p>
|
|
</p><p><b>YAML. </b>
|
|
</p><pre class="programlisting">request:
|
|
method: PUT
|
|
urlPath: /foo</pre><p>
|
|
</p><p><code class="literal">request</code> may contain <span class="strong"><strong>query parameters</strong></span>.</p><p><b>Groovy DSL. </b>
|
|
</p><pre class="programlisting">org.springframework.cloud.contract.spec.Contract.make {
|
|
request {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">//...</span>
|
|
method GET()
|
|
|
|
urlPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'/users'</span>) {
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// Each parameter is specified in form</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// `'paramName' : paramValue` where parameter value</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// may be a simple literal or one of matcher functions,</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// all of which are used in this example.</span>
|
|
queryParameters {
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// If a simple literal is used as value</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// default matcher function is used (equalTo)</span>
|
|
parameter <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'limit'</span>: <span class="hl-number">100</span>
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// `equalTo` function simply compares passed value</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// using identity operator (==).</span>
|
|
parameter <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'filter'</span>: equalTo(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"email"</span>)
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// `containing` function matches strings</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// that contains passed substring.</span>
|
|
parameter <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'gender'</span>: value(consumer(containing(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"[mf]"</span>)), producer(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'mf'</span>))
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// `matching` function tests parameter</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// against passed regular expression.</span>
|
|
parameter <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'offset'</span>: value(consumer(matching(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"[0-9]+"</span>)), producer(<span class="hl-number">123</span>))
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// `notMatching` functions tests if parameter</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// does not match passed regular expression.</span>
|
|
parameter <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'loginStartsWith'</span>: value(consumer(notMatching(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">".{0,2}"</span>)), producer(<span class="hl-number">3</span>))
|
|
}
|
|
}
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">//...</span>
|
|
}
|
|
|
|
response {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">//...</span>
|
|
status <span class="hl-number">200</span>
|
|
}
|
|
}</pre><p>
|
|
</p><p><b>YAML. </b>
|
|
</p><pre class="programlisting">request:
|
|
...
|
|
queryParameters:
|
|
a: b
|
|
b: c
|
|
headers:
|
|
foo: bar
|
|
fooReq: baz
|
|
cookies:
|
|
foo: bar
|
|
fooReq: baz
|
|
body:
|
|
foo: bar
|
|
matchers:
|
|
body:
|
|
- path: $.foo
|
|
type: by_regex
|
|
value: bar
|
|
headers:
|
|
- key: foo
|
|
regex: bar
|
|
response:
|
|
status: 200
|
|
fixedDelayMilliseconds: 1000
|
|
headers:
|
|
foo2: bar
|
|
foo3: foo33
|
|
fooRes: baz
|
|
body:
|
|
foo2: bar
|
|
foo3: baz
|
|
nullValue: null
|
|
matchers:
|
|
body:
|
|
- path: $.foo2
|
|
type: by_regex
|
|
value: bar
|
|
- path: $.foo3
|
|
type: by_command
|
|
value: executeMe($it)
|
|
- path: $.nullValue
|
|
type: by_null
|
|
value: null
|
|
headers:
|
|
- key: foo2
|
|
regex: bar
|
|
- key: foo3
|
|
command: andMeToo($it)
|
|
cookies:
|
|
- key: foo2
|
|
regex: bar
|
|
- key: foo3
|
|
predefined:</pre><p>
|
|
</p><p><code class="literal">request</code> may contain additional <span class="strong"><strong>request headers</strong></span>, as shown in the following example:</p><p><b>Groovy DSL. </b>
|
|
</p><pre class="programlisting">org.springframework.cloud.contract.spec.Contract.make {
|
|
request {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">//...</span>
|
|
method GET()
|
|
url <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"/foo"</span>
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// Each header is added in form `'Header-Name' : 'Header-Value'`.</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// there are also some helper methods</span>
|
|
headers {
|
|
header <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'key'</span>: <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'value'</span>
|
|
contentType(applicationJson())
|
|
}
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">//...</span>
|
|
}
|
|
|
|
response {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">//...</span>
|
|
status <span class="hl-number">200</span>
|
|
}
|
|
}</pre><p>
|
|
</p><p><b>YAML. </b>
|
|
</p><pre class="programlisting">request:
|
|
...
|
|
headers:
|
|
foo: bar
|
|
fooReq: baz</pre><p>
|
|
</p><p><code class="literal">request</code> may contain additional <span class="strong"><strong>request cookies</strong></span>, as shown in the following example:</p><p><b>Groovy DSL. </b>
|
|
</p><pre class="programlisting">org.springframework.cloud.contract.spec.Contract.make {
|
|
request {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">//...</span>
|
|
method GET()
|
|
url <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"/foo"</span>
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// Each Cookies is added in form `'Cookie-Key' : 'Cookie-Value'`.</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// there are also some helper methods</span>
|
|
cookies {
|
|
cookie <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'key'</span>: <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'value'</span>
|
|
cookie(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'another_key'</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'another_value'</span>)
|
|
}
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">//...</span>
|
|
}
|
|
|
|
response {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">//...</span>
|
|
status <span class="hl-number">200</span>
|
|
}
|
|
}</pre><p>
|
|
</p><p><b>YAML. </b>
|
|
</p><pre class="programlisting">request:
|
|
...
|
|
cookies:
|
|
foo: bar
|
|
fooReq: baz</pre><p>
|
|
</p><p><code class="literal">request</code> may contain a <span class="strong"><strong>request body</strong></span>:</p><p><b>Groovy DSL. </b>
|
|
</p><pre class="programlisting">org.springframework.cloud.contract.spec.Contract.make {
|
|
request {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">//...</span>
|
|
method GET()
|
|
url <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"/foo"</span>
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// Currently only JSON format of request body is supported.</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// Format will be determined from a header or body's content.</span>
|
|
body <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">''</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'{ "login" : "john", "name": "John The Contract" }'</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">''</span>
|
|
}
|
|
|
|
response {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">//...</span>
|
|
status <span class="hl-number">200</span>
|
|
}
|
|
}</pre><p>
|
|
</p><p><b>YAML. </b>
|
|
</p><pre class="programlisting">request:
|
|
...
|
|
body:
|
|
foo: bar</pre><p>
|
|
</p><p><code class="literal">request</code> may contain <span class="strong"><strong>multipart</strong></span> elements. To include multipart elements, use the
|
|
<code class="literal">multipart</code> method/section, as shown in the following examples</p><p><b>Groovy DSL. </b>
|
|
</p><pre class="programlisting"></pre><p>
|
|
</p><p><b>YAML. </b>
|
|
</p><pre class="programlisting">request:
|
|
method: PUT
|
|
url: /multipart
|
|
headers:
|
|
Content-Type: multipart/form-data;boundary=AaB03x
|
|
multipart:
|
|
params:
|
|
# key (parameter name), value (parameter value) pair
|
|
formParameter: '"formParameterValue"'
|
|
someBooleanParameter: true
|
|
named:
|
|
- paramName: file
|
|
fileName: filename.csv
|
|
fileContent: file content
|
|
matchers:
|
|
multipart:
|
|
params:
|
|
- key: formParameter
|
|
regex: ".+"
|
|
- key: someBooleanParameter
|
|
predefined: any_boolean
|
|
named:
|
|
- paramName: file
|
|
fileName:
|
|
predefined: non_empty
|
|
fileContent:
|
|
predefined: non_empty
|
|
response:
|
|
status: 200</pre><p>
|
|
</p><p>In the preceding example, we define parameters in either of two ways:</p><div class="itemizedlist"><p class="title"><b>Groovy DSL</b></p><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">Directly, by using the map notation, where the value can be a dynamic property (such as
|
|
<code class="literal">formParameter: $(consumer(…​), producer(…​))</code>).</li><li class="listitem">By using the <code class="literal">named(…​)</code> method that lets you set a named parameter. A named parameter
|
|
can set a <code class="literal">name</code> and <code class="literal">content</code>. You can call it either via a method with two arguments,
|
|
such as <code class="literal">named("fileName", "fileContent")</code>, or via a map notation, such as
|
|
<code class="literal">named(name: "fileName", content: "fileContent")</code>.</li></ul></div><div class="itemizedlist"><p class="title"><b>YAML</b></p><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">The multipart parameters are set via <code class="literal">multipart.params</code> section</li><li class="listitem">The named parameters (the <code class="literal">fileName</code> and <code class="literal">fileContent</code> for a given parameter name)
|
|
can be set via the <code class="literal">multipart.named</code> section. That section contains
|
|
the <code class="literal">paramName</code> (name of the parameter), <code class="literal">fileName</code> (name of the file),
|
|
<code class="literal">fileContent</code> (content of the file) fields</li><li class="listitem"><p class="simpara">The dynamic bits can be set via the <code class="literal">matchers.multipart</code> section</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: circle; "><li class="listitem">for parameters use the <code class="literal">params</code> section that can accept
|
|
<code class="literal">regex</code> or a <code class="literal">predefined</code> regular expression</li><li class="listitem">for named params use the <code class="literal">named</code> section where first you
|
|
define the parameter name via <code class="literal">paramName</code> and then you can pass the
|
|
parametrization of either <code class="literal">fileName</code> or <code class="literal">fileContent</code> via
|
|
<code class="literal">regex</code> or a <code class="literal">predefined</code> regular expression</li></ul></div></li></ul></div><p>From this contract, the generated test is as follows:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// given:</span>
|
|
MockMvcRequestSpecification request = given()
|
|
.header(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"Content-Type"</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"multipart/form-data;boundary=AaB03x"</span>)
|
|
.param(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"formParameter"</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"\"formParameterValue\""</span>)
|
|
.param(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"someBooleanParameter"</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"true"</span>)
|
|
.multiPart(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"file"</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"filename.csv"</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"file content"</span>.getBytes());
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// when:</span>
|
|
ResponseOptions response = given().spec(request)
|
|
.put(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"/multipart"</span>);
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// then:</span>
|
|
assertThat(response.statusCode()).isEqualTo(<span class="hl-number">200</span>);</pre><p>The WireMock stub is as follows:</p><pre class="programlisting"> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">''</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'
|
|
</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"request"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"url"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"/multipart"</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">,</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"method"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"PUT"</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">,</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"headers"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"Content-Type"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"matches"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"multipart/form-data;boundary=AaB03x.*"</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">}</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">},</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"bodyPatterns"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">[</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"matches"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">".*--(.*)\\r\\nContent-Disposition: form-data; name=\\"</span>formParameter\\<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"\\r\\n(Content-Type: .*\\r\\n)?(Content-Transfer-Encoding: .*\\r\\n)?(Content-Length: \\\\d+\\r\\n)?\\r\\n\\"</span>.+\\<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"\\r\\n--\\\\1.*"</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">},</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"matches"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">".*--(.*)\\r\\nContent-Disposition: form-data; name=\\"</span>someBooleanParameter\\<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"\\r\\n(Content-Type: .*\\r\\n)?(Content-Transfer-Encoding: .*\\r\\n)?(Content-Length: \\\\d+\\r\\n)?\\r\\n(true|false)\\r\\n--\\\\1.*"</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">},</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"matches"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">".*--(.*)\\r\\nContent-Disposition: form-data; name=\\"</span>file\\<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"; filename=\\"</span>[\\\\S\\\\s]+\\<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"\\r\\n(Content-Type: .*\\r\\n)?(Content-Transfer-Encoding: .*\\r\\n)?(Content-Length: \\\\d+\\r\\n)?\\r\\n[\\\\S\\\\s]+\\r\\n--\\\\1.*"</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">}</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">]</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">},</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"response"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"status"</span> : <span class="hl-number">200</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">,</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"transformers"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">[</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"response-template"</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">,</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"foo-transformer"</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">]</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">}</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">}</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">''</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'</span></pre></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_response" href="#_response"></a>94.4 Response</h2></div></div></div><p>The response must contain an <span class="strong"><strong>HTTP status code</strong></span> and may contain other information. The
|
|
following code shows an example:</p><p><b>Groovy DSL. </b>
|
|
</p><pre class="programlisting">org.springframework.cloud.contract.spec.Contract.make {
|
|
request {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">//...</span>
|
|
method GET()
|
|
url <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"/foo"</span>
|
|
}
|
|
response {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// Status code sent by the server</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// in response to request specified above.</span>
|
|
status OK()
|
|
}
|
|
}</pre><p>
|
|
</p><p><b>YAML. </b>
|
|
</p><pre class="programlisting">response:
|
|
...
|
|
status: 200</pre><p>
|
|
</p><p>Besides status, the response may contain <span class="strong"><strong>headers</strong></span>, <span class="strong"><strong>cookies</strong></span> and a <span class="strong"><strong>body</strong></span>, both of which are
|
|
specified the same way as in the request (see the previous paragraph).</p><div class="tip" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Tip"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Tip]" src="images/tip.png"></td><th align="left">Tip</th></tr><tr><td align="left" valign="top"><p>Via the Groovy DSL you can reference the <code class="literal">org.springframework.cloud.contract.spec.internal.HttpStatus</code>
|
|
methods to provide a meaningful status instead of a digit. E.g. you can call
|
|
<code class="literal">OK()</code> for a status <code class="literal">200</code> or <code class="literal">BAD_REQUEST()</code> for <code class="literal">400</code>.</p></td></tr></table></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_dynamic_properties" href="#_dynamic_properties"></a>94.5 Dynamic properties</h2></div></div></div><p>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.</p><p>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
|
|
<code class="literal">bodyMatchers</code>.</p><div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Note]" src="images/note.png"></td><th align="left">Note</th></tr><tr><td align="left" valign="top"><p>Before 2.0.0 these were set using <code class="literal">testMatchers</code> and <code class="literal">stubMatchers</code>,
|
|
check out the <a class="link" href="https://github.com/spring-cloud/spring-cloud-contract/wiki/Spring-Cloud-Contract-2.0-Migration-Guide" target="_top">migration guide</a> for more information.</p></td></tr></table></div><p>For YAML you can only use the <code class="literal">matchers</code> section.</p><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_dynamic_properties_inside_the_body" href="#_dynamic_properties_inside_the_body"></a>94.5.1 Dynamic properties inside the body</h3></div></div></div><div class="important" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Important"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Important]" src="images/important.png"></td><th align="left">Important</th></tr><tr><td align="left" valign="top"><p>This section is valid only for Groovy DSL. Check out the
|
|
<a class="xref" href="multi_contract-dsl.html#contract-matchers" title="94.5.7 Dynamic Properties in the Matchers Sections">Section 94.5.7, “Dynamic Properties in the Matchers Sections”</a> section for YAML examples of a similar feature.</p></td></tr></table></div><p>You can set the properties inside the body either with the <code class="literal">value</code> method or, if you use
|
|
the Groovy map notation, with <code class="literal">$()</code>. The following example shows how to set dynamic
|
|
properties with the value method:</p><pre class="programlisting">value(consumer(...), producer(...))
|
|
value(c(...), p(...))
|
|
value(stub(...), test(...))
|
|
value(client(...), server(...))</pre><p>The following example shows how to set dynamic properties with <code class="literal">$()</code>:</p><pre class="programlisting">$(consumer(...), producer(...))
|
|
$(c(...), p(...))
|
|
$(stub(...), test(...))
|
|
$(client(...), server(...))</pre><p>Both approaches work equally well. <code class="literal">stub</code> and <code class="literal">client</code> methods are aliases over the <code class="literal">consumer</code>
|
|
method. Subsequent sections take a closer look at what you can do with those values.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_regular_expressions" href="#_regular_expressions"></a>94.5.2 Regular expressions</h3></div></div></div><div class="important" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Important"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Important]" src="images/important.png"></td><th align="left">Important</th></tr><tr><td align="left" valign="top"><p>This section is valid only for Groovy DSL. Check out the
|
|
<a class="xref" href="multi_contract-dsl.html#contract-matchers" title="94.5.7 Dynamic Properties in the Matchers Sections">Section 94.5.7, “Dynamic Properties in the Matchers Sections”</a> section for YAML examples of a similar feature.</p></td></tr></table></div><p>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.</p><p>The following example shows how to use regular expressions to write a request:</p><pre class="programlisting">org.springframework.cloud.contract.spec.Contract.make {
|
|
request {
|
|
method(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'GET'</span>)
|
|
url $(consumer(~/\/[<span class="hl-number">0</span>-<span class="hl-number">9</span>]{<span class="hl-number">2</span>}/), producer(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'/12'</span>))
|
|
}
|
|
response {
|
|
status OK()
|
|
body(
|
|
id: $(anyNumber()),
|
|
surname: $(
|
|
consumer(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'Kowalsky'</span>),
|
|
producer(regex(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'[a-zA-Z]+'</span>))
|
|
),
|
|
name: <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'Jan'</span>,
|
|
created: $(consumer(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'2014-02-02 12:23:43'</span>), producer(execute(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'currentDate(it)'</span>))),
|
|
correlationId: value(consumer(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'5d1f9fef-e0dc-4f3d-a7e4-72d2220dd827'</span>),
|
|
producer(regex(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'[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}'</span>))
|
|
)
|
|
)
|
|
headers {
|
|
header <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'Content-Type'</span>: <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'text/plain'</span>
|
|
}
|
|
}
|
|
}</pre><p>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:</p><pre class="programlisting">org.springframework.cloud.contract.spec.Contract.make {
|
|
request {
|
|
method <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'PUT'</span>
|
|
url value(consumer(regex(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'/foo/[0-9]{5}'</span>)))
|
|
body([
|
|
requestElement: $(consumer(regex(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'[0-9]{5}'</span>)))
|
|
])
|
|
headers {
|
|
header(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'header'</span>, $(consumer(regex(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'application\\/vnd\\.fraud\\.v1\\+json;.*'</span>))))
|
|
}
|
|
}
|
|
response {
|
|
status OK()
|
|
body([
|
|
responseElement: $(producer(regex(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'[0-9]{7}'</span>)))
|
|
])
|
|
headers {
|
|
contentType(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"application/vnd.fraud.v1+json"</span>)
|
|
}
|
|
}
|
|
}</pre><p>In the preceding example, the opposite side of the communication has the respective data
|
|
generated for request and response.</p><p>Spring Cloud Contract comes with a series of predefined regular expressions that you can
|
|
use in your contracts, as shown in the following example:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">protected</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">final</span> Pattern TRUE_OR_FALSE = Pattern.compile(/(true|false)/)
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">protected</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">final</span> Pattern ALPHA_NUMERIC = Pattern.compile(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'[a-zA-Z0-9]+'</span>)
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">protected</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">final</span> Pattern ONLY_ALPHA_UNICODE = Pattern.compile(/[\p{L}]*/)
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">protected</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">final</span> Pattern NUMBER = Pattern.compile(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'-?(\\d*\\.\\d+|\\d+)'</span>)
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">protected</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">final</span> Pattern INTEGER = Pattern.compile(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'-?(\\d+)'</span>)
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">protected</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">final</span> Pattern POSITIVE_INT = Pattern.compile(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'([1-9]\\d*)'</span>)
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">protected</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">final</span> Pattern DOUBLE = Pattern.compile(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'-?(\\d*\\.\\d+)'</span>)
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">protected</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">final</span> Pattern HEX = Pattern.compile(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'[a-fA-F0-9]+'</span>)
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">protected</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">final</span> Pattern IP_ADDRESS = Pattern.
|
|
compile(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'([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])'</span>)
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">protected</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">final</span> Pattern HOSTNAME_PATTERN = Pattern.
|
|
compile(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'((http[s]?|ftp):/)/?([^:/\\s]+)(:[0-9]{1,5})?'</span>)
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">protected</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">final</span> Pattern EMAIL = Pattern.
|
|
compile(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6}'</span>)
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">protected</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">final</span> Pattern URL = UrlHelper.URL
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">protected</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">final</span> Pattern HTTPS_URL = UrlHelper.HTTPS_URL
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">protected</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">final</span> Pattern UUID = Pattern.
|
|
compile(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}'</span>)
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">protected</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">final</span> Pattern ANY_DATE = Pattern.
|
|
compile(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'(\\d\\d\\d\\d)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])'</span>)
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">protected</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">final</span> Pattern ANY_DATE_TIME = Pattern.
|
|
compile(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'([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])'</span>)
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">protected</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">final</span> Pattern ANY_TIME = Pattern.
|
|
compile(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])'</span>)
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">protected</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">final</span> Pattern NON_EMPTY = Pattern.compile(/[\S\s]+/)
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">protected</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">final</span> Pattern NON_BLANK = Pattern.compile(/^\s*\S[\S\s]*/)
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">protected</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">final</span> Pattern ISO8601_WITH_OFFSET = Pattern.
|
|
compile(/([<span class="hl-number">0</span>-<span class="hl-number">9</span>]{<span class="hl-number">4</span>})-(<span class="hl-number">1</span>[<span class="hl-number">0</span>-<span class="hl-number">2</span>]|<span class="hl-number">0</span>[<span class="hl-number">1</span>-<span class="hl-number">9</span>])-(<span class="hl-number">3</span>[<span class="hl-number">01</span>]|<span class="hl-number">0</span>[<span class="hl-number">1</span>-<span class="hl-number">9</span>]|[<span class="hl-number">12</span>][<span class="hl-number">0</span>-<span class="hl-number">9</span>])T(<span class="hl-number">2</span>[<span class="hl-number">0</span>-<span class="hl-number">3</span>]|[<span class="hl-number">01</span>][<span class="hl-number">0</span>-<span class="hl-number">9</span>]):([<span class="hl-number">0</span>-<span class="hl-number">5</span>][<span class="hl-number">0</span>-<span class="hl-number">9</span>]):([<span class="hl-number">0</span>-<span class="hl-number">5</span>][<span class="hl-number">0</span>-<span class="hl-number">9</span>])(\.\d{<span class="hl-number">3</span>})?(Z|[+-][<span class="hl-number">01</span>]\d:[<span class="hl-number">0</span>-<span class="hl-number">5</span>]\d)/)
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">protected</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> Pattern anyOf(String... values) {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> Pattern.compile(values.collect({ <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"^$it\$"</span> }).join(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"|"</span>))
|
|
}
|
|
|
|
RegexProperty onlyAlphaUnicode() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> RegexProperty(ONLY_ALPHA_UNICODE).asString()
|
|
}
|
|
|
|
RegexProperty alphaNumeric() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> RegexProperty(ALPHA_NUMERIC).asString()
|
|
}
|
|
|
|
RegexProperty number() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> RegexProperty(NUMBER).asDouble()
|
|
}
|
|
|
|
RegexProperty positiveInt() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> RegexProperty(POSITIVE_INT).asInteger()
|
|
}
|
|
|
|
RegexProperty anyBoolean() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> RegexProperty(TRUE_OR_FALSE).asBooleanType()
|
|
}
|
|
|
|
RegexProperty anInteger() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> RegexProperty(INTEGER).asInteger()
|
|
}
|
|
|
|
RegexProperty aDouble() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> RegexProperty(DOUBLE).asDouble()
|
|
}
|
|
|
|
RegexProperty ipAddress() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> RegexProperty(IP_ADDRESS).asString()
|
|
}
|
|
|
|
RegexProperty hostname() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> RegexProperty(HOSTNAME_PATTERN).asString()
|
|
}
|
|
|
|
RegexProperty email() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> RegexProperty(EMAIL).asString()
|
|
}
|
|
|
|
RegexProperty url() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> RegexProperty(URL).asString()
|
|
}
|
|
|
|
RegexProperty httpsUrl() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> RegexProperty(HTTPS_URL).asString()
|
|
}
|
|
|
|
RegexProperty uuid() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> RegexProperty(UUID).asString()
|
|
}
|
|
|
|
RegexProperty isoDate() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> RegexProperty(ANY_DATE).asString()
|
|
}
|
|
|
|
RegexProperty isoDateTime() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> RegexProperty(ANY_DATE_TIME).asString()
|
|
}
|
|
|
|
RegexProperty isoTime() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> RegexProperty(ANY_TIME).asString()
|
|
}
|
|
|
|
RegexProperty iso8601WithOffset() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> RegexProperty(ISO8601_WITH_OFFSET).asString()
|
|
}
|
|
|
|
RegexProperty nonEmpty() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> RegexProperty(NON_EMPTY).asString()
|
|
}
|
|
|
|
RegexProperty nonBlank() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> RegexProperty(NON_BLANK).asString()
|
|
}</pre><p>In your contract, you can use it as shown in the following example:</p><pre class="programlisting">Contract dslWithOptionalsInString = Contract.make {
|
|
priority <span class="hl-number">1</span>
|
|
request {
|
|
method POST()
|
|
url <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'/users/password'</span>
|
|
headers {
|
|
contentType(applicationJson())
|
|
}
|
|
body(
|
|
email: $(consumer(optional(regex(email()))), producer(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'abc@abc.com'</span>)),
|
|
callback_url: $(consumer(regex(hostname())), producer(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'http://partners.com'</span>))
|
|
)
|
|
}
|
|
response {
|
|
status <span class="hl-number">404</span>
|
|
headers {
|
|
contentType(applicationJson())
|
|
}
|
|
body(
|
|
code: value(consumer(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"123123"</span>), producer(optional(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"123123"</span>))),
|
|
message: <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"User not found by email = [${value(producer(regex(email())), consumer('not.existing@user.com'))}]"</span>
|
|
)
|
|
}
|
|
}</pre><p>To make matters even simpler you can use a set of predefined objects that will automatically assume that you want a regular expression to be passed.
|
|
All of those methods start with <code class="literal">any</code> prefix:</p><pre class="programlisting">T anyAlphaUnicode()
|
|
|
|
T anyAlphaNumeric()
|
|
|
|
T anyNumber()
|
|
|
|
T anyInteger()
|
|
|
|
T anyPositiveInt()
|
|
|
|
T anyDouble()
|
|
|
|
T anyHex()
|
|
|
|
T aBoolean()
|
|
|
|
T anyIpAddress()
|
|
|
|
T anyHostname()
|
|
|
|
T anyEmail()
|
|
|
|
T anyUrl()
|
|
|
|
T anyHttpsUrl()
|
|
|
|
T anyUuid()
|
|
|
|
T anyDate()
|
|
|
|
T anyDateTime()
|
|
|
|
T anyTime()
|
|
|
|
T anyIso8601WithOffset()
|
|
|
|
T anyNonBlankString()
|
|
|
|
T anyNonEmptyString()
|
|
|
|
T anyOf(String... values)</pre><p>and this is an example of how you can reference those methods:</p><pre class="programlisting">Contract contractDsl = Contract.make {
|
|
label <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'trigger_event'</span>
|
|
input {
|
|
triggeredBy(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'toString()'</span>)
|
|
}
|
|
outputMessage {
|
|
sentTo <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'topic.rateablequote'</span>
|
|
body([
|
|
alpha : $(anyAlphaUnicode()),
|
|
number : $(anyNumber()),
|
|
anInteger : $(anyInteger()),
|
|
positiveInt : $(anyPositiveInt()),
|
|
aDouble : $(anyDouble()),
|
|
aBoolean : $(aBoolean()),
|
|
ip : $(anyIpAddress()),
|
|
hostname : $(anyHostname()),
|
|
email : $(anyEmail()),
|
|
url : $(anyUrl()),
|
|
httpsUrl : $(anyHttpsUrl()),
|
|
uuid : $(anyUuid()),
|
|
date : $(anyDate()),
|
|
dateTime : $(anyDateTime()),
|
|
time : $(anyTime()),
|
|
iso8601WithOffset: $(anyIso8601WithOffset()),
|
|
nonBlankString : $(anyNonBlankString()),
|
|
nonEmptyString : $(anyNonEmptyString()),
|
|
anyOf : $(anyOf(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'foo'</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'bar'</span>))
|
|
])
|
|
}
|
|
}</pre></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_passing_optional_parameters" href="#_passing_optional_parameters"></a>94.5.3 Passing Optional Parameters</h3></div></div></div><div class="important" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Important"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Important]" src="images/important.png"></td><th align="left">Important</th></tr><tr><td align="left" valign="top"><p>This section is valid only for Groovy DSL. Check out the
|
|
<a class="xref" href="multi_contract-dsl.html#contract-matchers" title="94.5.7 Dynamic Properties in the Matchers Sections">Section 94.5.7, “Dynamic Properties in the Matchers Sections”</a> section for YAML examples of a similar feature.</p></td></tr></table></div><p>It is possible to provide optional parameters in your contract. However, you can provide
|
|
optional parameters only for the following:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><span class="emphasis"><em>STUB</em></span> side of the Request</li><li class="listitem"><span class="emphasis"><em>TEST</em></span> side of the Response</li></ul></div><p>The following example shows how to provide optional parameters:</p><pre class="programlisting">org.springframework.cloud.contract.spec.Contract.make {
|
|
priority <span class="hl-number">1</span>
|
|
request {
|
|
method <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'POST'</span>
|
|
url <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'/users/password'</span>
|
|
headers {
|
|
contentType(applicationJson())
|
|
}
|
|
body(
|
|
email: $(consumer(optional(regex(email()))), producer(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'abc@abc.com'</span>)),
|
|
callback_url: $(consumer(regex(hostname())), producer(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'https://partners.com'</span>))
|
|
)
|
|
}
|
|
response {
|
|
status <span class="hl-number">404</span>
|
|
headers {
|
|
header <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'Content-Type'</span>: <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'application/json'</span>
|
|
}
|
|
body(
|
|
code: value(consumer(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"123123"</span>), producer(optional(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"123123"</span>)))
|
|
)
|
|
}
|
|
}</pre><p>By wrapping a part of the body with the <code class="literal">optional()</code> method, you create a regular
|
|
expression that must be present 0 or more times.</p><p>If you use Spock for, the following test would be generated from the previous example:</p><pre class="programlisting"> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">""</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"
|
|
</span> given:
|
|
def request = given()
|
|
.header(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"Content-Type"</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"application/json"</span>)
|
|
.body(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">''</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'{"email":"abc@abc.com","callback_url":"https://partners.com"}'</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">''</span>)
|
|
|
|
when:
|
|
def response = given().spec(request)
|
|
.post(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"/users/password"</span>)
|
|
|
|
then:
|
|
response.statusCode == <span class="hl-number">404</span>
|
|
response.header(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'Content-Type'</span>) == <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'application/json'</span>
|
|
and:
|
|
DocumentContext parsedJson = JsonPath.parse(response.body.asString())
|
|
assertThatJson(parsedJson).field(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"['code']"</span>).matches(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"(123123)?"</span>)
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">""</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"</span></pre><p>The following stub would also be generated:</p><pre class="programlisting"> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">''</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'
|
|
</span>{
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"request"</span> : {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"url"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"/users/password"</span>,
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"method"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"POST"</span>,
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"bodyPatterns"</span> : [ {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"matchesJsonPath"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$[?(@.['email'] =~ /([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,6})?/)]"</span>
|
|
}, {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"matchesJsonPath"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$[?(@.['callback_url'] =~ /((http[s]?|ftp):\\\\/)\\\\/?([^:\\\\/\\\\s]+)(:[0-9]{1,5})?/)]"</span>
|
|
} ],
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"headers"</span> : {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"Content-Type"</span> : {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"equalTo"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"application/json"</span>
|
|
}
|
|
}
|
|
},
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"response"</span> : {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"status"</span> : <span class="hl-number">404</span>,
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"body"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"{\\"</span>code\\<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">":\\"</span><span class="hl-number">123123</span>\\<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">",\\"</span>message\\<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">":\\"</span>User not found by email == [not.existing<em><span class="hl-annotation" style="color: gray">@user.com]\\"}",</span></em>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"headers"</span> : {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"Content-Type"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"application/json"</span>
|
|
}
|
|
},
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"priority"</span> : <span class="hl-number">1</span>
|
|
}
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">''</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'</span></pre></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_executing_custom_methods_on_the_server_side" href="#_executing_custom_methods_on_the_server_side"></a>94.5.4 Executing Custom Methods on the Server Side</h3></div></div></div><div class="important" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Important"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Important]" src="images/important.png"></td><th align="left">Important</th></tr><tr><td align="left" valign="top"><p>This section is valid only for Groovy DSL. Check out the
|
|
<a class="xref" href="multi_contract-dsl.html#contract-matchers" title="94.5.7 Dynamic Properties in the Matchers Sections">Section 94.5.7, “Dynamic Properties in the Matchers Sections”</a> section for YAML examples of a similar feature.</p></td></tr></table></div><p>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:</p><pre class="programlisting">org.springframework.cloud.contract.spec.Contract.make {
|
|
request {
|
|
method <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'PUT'</span>
|
|
url $(consumer(regex(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'^/api/[0-9]{2}$'</span>)), producer(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'/api/12'</span>))
|
|
headers {
|
|
header <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'Content-Type'</span>: <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'application/json'</span>
|
|
}
|
|
body <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">''</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'\
|
|
</span> [{
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"text"</span>: <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"Gonna see you at Warsaw"</span>
|
|
}]
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">''</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'
|
|
</span> }
|
|
response {
|
|
body(
|
|
path: $(consumer(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'/api/12'</span>), producer(regex(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'^/api/[0-9]{2}$'</span>))),
|
|
correlationId: $(consumer(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'1223456'</span>), producer(execute(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'isProperCorrelationId($it)'</span>)))
|
|
)
|
|
status OK()
|
|
}
|
|
}</pre><p>The following code shows the base class portion of the test case:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">abstract</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span> BaseMockMvcSpec <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">extends</span> Specification {
|
|
|
|
def setup() {
|
|
RestAssuredMockMvc.standaloneSetup(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> PairIdController())
|
|
}
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">void</span> isProperCorrelationId(Integer correlationId) {
|
|
assert correlationId == <span class="hl-number">123456</span>
|
|
}
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">void</span> isEmpty(String value) {
|
|
assert value == null
|
|
}
|
|
|
|
}</pre><div class="important" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Important"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Important]" src="images/important.png"></td><th align="left">Important</th></tr><tr><td align="left" valign="top"><p>You cannot use both a String and <code class="literal">execute</code> to perform concatenation. For
|
|
example, calling <code class="literal">header('Authorization', 'Bearer ' + execute('authToken()'))</code> leads to
|
|
improper results. Instead, call <code class="literal">header('Authorization', execute('authToken()'))</code> and
|
|
ensure that the <code class="literal">authToken()</code> method returns everything you need.</p></td></tr></table></div><p>The type of the object read from the JSON can be one of the following, depending on the
|
|
JSON path:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><code class="literal">String</code>: If you point to a <code class="literal">String</code> value in the JSON.</li><li class="listitem"><code class="literal">JSONArray</code>: If you point to a <code class="literal">List</code> in the JSON.</li><li class="listitem"><code class="literal">Map</code>: If you point to a <code class="literal">Map</code> in the JSON.</li><li class="listitem"><code class="literal">Number</code>: If you point to <code class="literal">Integer</code>, <code class="literal">Double</code> etc. in the JSON.</li><li class="listitem"><code class="literal">Boolean</code>: If you point to a <code class="literal">Boolean</code> in the JSON.</li></ul></div><p>In the request part of the contract, you can specify that the <code class="literal">body</code> should be taken from
|
|
a method.</p><div class="important" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Important"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Important]" src="images/important.png"></td><th align="left">Important</th></tr><tr><td align="left" valign="top"><p>You must provide both the consumer and the producer side. The <code class="literal">execute</code> part
|
|
is applied for the whole body - not for parts of it.</p></td></tr></table></div><p>The following example shows how to read an object from JSON:</p><pre class="programlisting">Contract contractDsl = Contract.make {
|
|
request {
|
|
method <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'GET'</span>
|
|
url <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'/something'</span>
|
|
body(
|
|
$(c(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'foo'</span>), p(execute(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'hashCode()'</span>)))
|
|
)
|
|
}
|
|
response {
|
|
status OK()
|
|
}
|
|
}</pre><p>The preceding example results in calling the <code class="literal">hashCode()</code> method in the request body.
|
|
It should resemble the following code:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// given:</span>
|
|
MockMvcRequestSpecification request = given()
|
|
.body(hashCode());
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// when:</span>
|
|
ResponseOptions response = given().spec(request)
|
|
.get(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"/something"</span>);
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// then:</span>
|
|
assertThat(response.statusCode()).isEqualTo(<span class="hl-number">200</span>);</pre></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_referencing_the_request_from_the_response" href="#_referencing_the_request_from_the_response"></a>94.5.5 Referencing the Request from the Response</h3></div></div></div><p>The best situation is to provide fixed values, but sometimes you need to reference a
|
|
request in your response.</p><p>If you’re writing contracts using Groovy DSL, you can use the <code class="literal">fromRequest()</code> method, which lets
|
|
you reference a bunch of elements from the HTTP request. You can use the following
|
|
options:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><code class="literal">fromRequest().url()</code>: Returns the request URL and query parameters.</li><li class="listitem"><code class="literal">fromRequest().query(String key)</code>: Returns the first query parameter with a given name.</li><li class="listitem"><code class="literal">fromRequest().query(String key, int index)</code>: Returns the nth query parameter with a
|
|
given name.</li><li class="listitem"><code class="literal">fromRequest().path()</code>: Returns the full path.</li><li class="listitem"><code class="literal">fromRequest().path(int index)</code>: Returns the nth path element.</li><li class="listitem"><code class="literal">fromRequest().header(String key)</code>: Returns the first header with a given name.</li><li class="listitem"><code class="literal">fromRequest().header(String key, int index)</code>: Returns the nth header with a given name.</li><li class="listitem"><code class="literal">fromRequest().body()</code>: Returns the full request body.</li><li class="listitem"><code class="literal">fromRequest().body(String jsonPath)</code>: Returns the element from the request that
|
|
matches the JSON Path.</li></ul></div><p>If you’re using the YAML contract definition you have to use the
|
|
<a class="link" href="https://handlebarsjs.com/" target="_top">Handlebars</a> <code class="literal">{{{ }}}</code> notation with custom, Spring Cloud Contract
|
|
functions to achieve this.</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><code class="literal">{{{ request.url }}}</code>: Returns the request URL and query parameters.</li><li class="listitem"><code class="literal">{{{ request.query.key.[index] }}}</code>: Returns the nth query parameter with a given name.
|
|
E.g. for key <code class="literal">foo</code>, first entry <code class="literal">{{{ request.query.foo.[0] }}}</code></li><li class="listitem"><code class="literal">{{{ request.path }}}</code>: Returns the full path.</li><li class="listitem"><code class="literal">{{{ request.path.[index] }}}</code>: Returns the nth path element. E.g.
|
|
for first entry <code class="literal">`</code>{{{ request.path.[0] }}}</li><li class="listitem"><code class="literal">{{{ request.headers.key }}}</code>: Returns the first header with a given name.</li><li class="listitem"><code class="literal">{{{ request.headers.key.[index] }}}</code>: Returns the nth header with a given name.</li><li class="listitem"><code class="literal">{{{ request.body }}}</code>: Returns the full request body.</li><li class="listitem"><code class="literal">{{{ jsonpath this 'your.json.path' }}}</code>: Returns the element from the request that
|
|
matches the JSON Path. E.g. for json path <code class="literal">$.foo</code> - <code class="literal">{{{ jsonpath this '$.foo' }}}</code></li></ul></div><p>Consider the following contract:</p><p><b>Groovy DSL. </b>
|
|
</p><pre class="programlisting"></pre><p>
|
|
</p><p><b>YAML. </b>
|
|
</p><pre class="programlisting">request:
|
|
method: GET
|
|
url: /api/v1/xxxx
|
|
queryParameters:
|
|
foo:
|
|
- bar
|
|
- bar2
|
|
headers:
|
|
Authorization:
|
|
- secret
|
|
- secret2
|
|
body:
|
|
foo: bar
|
|
baz: 5
|
|
response:
|
|
status: 200
|
|
headers:
|
|
Authorization: "foo {{{ request.headers.Authorization.0 }}} bar"
|
|
body:
|
|
url: "{{{ request.url }}}"
|
|
path: "{{{ request.path }}}"
|
|
pathIndex: "{{{ request.path.1 }}}"
|
|
param: "{{{ request.query.foo }}}"
|
|
paramIndex: "{{{ request.query.foo.1 }}}"
|
|
authorization: "{{{ request.headers.Authorization.0 }}}"
|
|
authorization2: "{{{ request.headers.Authorization.1 }}"
|
|
fullBody: "{{{ request.body }}}"
|
|
responseFoo: "{{{ jsonpath this '$.foo' }}}"
|
|
responseBaz: "{{{ jsonpath this '$.baz' }}}"
|
|
responseBaz2: "Bla bla {{{ jsonpath this '$.foo' }}} bla bla"</pre><p>
|
|
</p><p>Running a JUnit test generation leads to a test that resembles the following example:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// given:</span>
|
|
MockMvcRequestSpecification request = given()
|
|
.header(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"Authorization"</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"secret"</span>)
|
|
.header(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"Authorization"</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"secret2"</span>)
|
|
.body(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"{\"foo\":\"bar\",\"baz\":5}"</span>);
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// when:</span>
|
|
ResponseOptions response = given().spec(request)
|
|
.queryParam(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"foo"</span>,<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"bar"</span>)
|
|
.queryParam(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"foo"</span>,<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"bar2"</span>)
|
|
.get(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"/api/v1/xxxx"</span>);
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// then:</span>
|
|
assertThat(response.statusCode()).isEqualTo(<span class="hl-number">200</span>);
|
|
assertThat(response.header(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"Authorization"</span>)).isEqualTo(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"foo secret bar"</span>);
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// and:</span>
|
|
DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());
|
|
assertThatJson(parsedJson).field(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"['fullBody']"</span>).isEqualTo(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"{\"foo\":\"bar\",\"baz\":5}"</span>);
|
|
assertThatJson(parsedJson).field(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"['authorization']"</span>).isEqualTo(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"secret"</span>);
|
|
assertThatJson(parsedJson).field(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"['authorization2']"</span>).isEqualTo(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"secret2"</span>);
|
|
assertThatJson(parsedJson).field(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"['path']"</span>).isEqualTo(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"/api/v1/xxxx"</span>);
|
|
assertThatJson(parsedJson).field(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"['param']"</span>).isEqualTo(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"bar"</span>);
|
|
assertThatJson(parsedJson).field(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"['paramIndex']"</span>).isEqualTo(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"bar2"</span>);
|
|
assertThatJson(parsedJson).field(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"['pathIndex']"</span>).isEqualTo(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"v1"</span>);
|
|
assertThatJson(parsedJson).field(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"['responseBaz']"</span>).isEqualTo(<span class="hl-number">5</span>);
|
|
assertThatJson(parsedJson).field(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"['responseFoo']"</span>).isEqualTo(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"bar"</span>);
|
|
assertThatJson(parsedJson).field(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"['url']"</span>).isEqualTo(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"/api/v1/xxxx?foo=bar&foo=bar2"</span>);
|
|
assertThatJson(parsedJson).field(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"['responseBaz2']"</span>).isEqualTo(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"Bla bla bar bla bla"</span>);</pre><p>As you can see, elements from the request have been properly referenced in the response.</p><p>The generated WireMock stub should resemble the following example:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"request"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"urlPath"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"/api/v1/xxxx"</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">,</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"method"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"POST"</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">,</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"headers"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"Authorization"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"equalTo"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"secret2"</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">}</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">},</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"queryParameters"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"foo"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"equalTo"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"bar2"</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">}</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">},</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"bodyPatterns"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">[</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"matchesJsonPath"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$[?(@.['baz'] == 5)]"</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">},</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"matchesJsonPath"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$[?(@.['foo'] == 'bar')]"</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">}</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">]</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">},</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"response"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"status"</span> : <span class="hl-number">200</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">,</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"body"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"{\"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]}}}\"}"</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">,</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"headers"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"Authorization"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"{{{request.headers.Authorization.[0]}}};foo"</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">},</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"transformers"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">[</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"response-template"</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">]</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">}</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">}</span></pre><p>Sending a request such as the one presented in the <code class="literal">request</code> part of the contract results
|
|
in sending the following response body:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"url"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"/api/v1/xxxx?foo=bar&foo=bar2"</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">,</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"path"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"/api/v1/xxxx"</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">,</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"pathIndex"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"v1"</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">,</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"param"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"bar"</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">,</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"paramIndex"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"bar2"</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">,</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"authorization"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"secret"</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">,</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"authorization2"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"secret2"</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">,</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"fullBody"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"{\"foo\":\"bar\",\"baz\":5}"</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">,</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"responseFoo"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"bar"</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">,</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"responseBaz"</span> : <span class="hl-number">5</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">,</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"responseBaz2"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"Bla bla bar bla bla"</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">}</span></pre><div class="important" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Important"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Important]" src="images/important.png"></td><th align="left">Important</th></tr><tr><td align="left" valign="top"><p>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
|
|
<code class="literal">response-template</code> response transformer. It uses Handlebars to convert the Mustache <code class="literal">{{{ }}}</code> templates into
|
|
proper values. Additionally, it registers two helper functions:</p></td></tr></table></div><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><code class="literal">escapejsonbody</code>: Escapes the request body in a format that can be embedded in a JSON.</li><li class="listitem"><code class="literal">jsonpath</code>: For a given parameter, find an object in the request body.</li></ul></div></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_registering_your_own_wiremock_extension" href="#_registering_your_own_wiremock_extension"></a>94.5.6 Registering Your Own WireMock Extension</h3></div></div></div><p>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
|
|
<code class="literal">org.springframework.cloud.contract.verifier.dsl.wiremock.WireMockExtensions</code> interface.
|
|
Since we use the spring.factories extension approach, you can create an entry in
|
|
<code class="literal">META-INF/spring.factories</code> file similar to the following:</p><pre class="programlisting">org.springframework.cloud.contract.verifier.dsl.wiremock.WireMockExtensions=\
|
|
org.springframework.cloud.contract.stubrunner.provider.wiremock.TestWireMockExtensions
|
|
org.springframework.cloud.contract.spec.ContractConverter=\
|
|
org.springframework.cloud.contract.stubrunner.TestCustomYamlContractConverter</pre><p>The following is an example of a custom extension:</p><p><b>TestWireMockExtensions.groovy. </b>
|
|
</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">/*
|
|
* Copyright 2013-2019 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
|
|
*
|
|
* https://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.
|
|
*/</span>
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">package</span> org.springframework.cloud.contract.verifier.dsl.wiremock
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> com.github.tomakehurst.wiremock.extension.Extension
|
|
|
|
<strong class="hl-tag" style="color: blue">/**
|
|
* Extension that registers the default transformer and the custom one
|
|
*/</strong>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span> TestWireMockExtensions <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">implements</span> WireMockExtensions {
|
|
<em><span class="hl-annotation" style="color: gray">@Override</span></em>
|
|
List<Extension> extensions() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> [
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> DefaultResponseTransformer(),
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> CustomExtension()
|
|
]
|
|
}
|
|
}
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span> CustomExtension <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">implements</span> Extension {
|
|
|
|
<em><span class="hl-annotation" style="color: gray">@Override</span></em>
|
|
String getName() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"foo-transformer"</span>
|
|
}
|
|
}</pre><p>
|
|
</p><div class="important" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Important"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Important]" src="images/important.png"></td><th align="left">Important</th></tr><tr><td align="left" valign="top"><p>Remember to override the <code class="literal">applyGlobally()</code> method and set it to <code class="literal">false</code> if you
|
|
want the transformation to be applied only for a mapping that explicitly requires it.</p></td></tr></table></div></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="contract-matchers" href="#contract-matchers"></a>94.5.7 Dynamic Properties in the Matchers Sections</h3></div></div></div><p>If you work with <a class="link" href="https://docs.pact.io/" target="_top">Pact</a>, 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.</p><p>You can use the <code class="literal">bodyMatchers</code> section for two reasons:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">Define the dynamic values that should end up in a stub.
|
|
You can set it in the <code class="literal">request</code> or <code class="literal">inputMessage</code> part of your contract.</li><li class="listitem">Verify the result of your test.
|
|
This section is present in the <code class="literal">response</code> or <code class="literal">outputMessage</code> side of the
|
|
contract.</li></ul></div><p>Currently, Spring Cloud Contract Verifier supports only JSON Path-based matchers with the
|
|
following matching possibilities:</p><div class="itemizedlist"><p class="title"><b>Groovy DSL</b></p><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><p class="simpara">For the stubs(in tests on the Consumer’s side):</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: circle; "><li class="listitem"><code class="literal">byEquality()</code>: The value taken from the consumer’s request via the provided JSON Path must be
|
|
equal to the value provided in the contract.</li><li class="listitem"><code class="literal">byRegex(…​)</code>: The value taken from the consumer’s request via the provided JSON Path must
|
|
match the regex. You can also pass the type of the expected matched value (e.g. <code class="literal">asString()</code>, <code class="literal">asLong()</code> etc.)</li><li class="listitem"><code class="literal">byDate()</code>: The value taken from the consumer’s request via the provided JSON Path must
|
|
match the regex for an ISO Date value.</li><li class="listitem"><code class="literal">byTimestamp()</code>: The value taken from the consumer’s request via the provided JSON Path must
|
|
match the regex for an ISO DateTime value.</li><li class="listitem"><code class="literal">byTime()</code>: The value taken from the consumer’s request via the provided JSON Path must
|
|
match the regex for an ISO Time value.</li></ul></div></li><li class="listitem"><p class="simpara">For the verification(in generated tests on the Producer’s side):</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: circle; "><li class="listitem"><code class="literal">byEquality()</code>: The value taken from the producer’s response via the provided JSON Path must be
|
|
equal to the provided value in the contract.</li><li class="listitem"><code class="literal">byRegex(…​)</code>: The value taken from the producer’s response via the provided JSON Path must
|
|
match the regex.</li><li class="listitem"><code class="literal">byDate()</code>: The value taken from the producer’s response via the provided JSON Path must match
|
|
the regex for an ISO Date value.</li><li class="listitem"><code class="literal">byTimestamp()</code>: The value taken from the producer’s response via the provided JSON Path must
|
|
match the regex for an ISO DateTime value.</li><li class="listitem"><code class="literal">byTime()</code>: The value taken from the producer’s response via the provided JSON Path must match
|
|
the regex for an ISO Time value.</li><li class="listitem"><code class="literal">byType()</code>: The value taken from the producer’s 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.
|
|
<code class="literal">byType</code> can take a closure, in which you can set <code class="literal">minOccurrence</code> and <code class="literal">maxOccurrence</code>. For the request side, you should use the closure to assert size of the collection.
|
|
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 <code class="literal">byCommand(…​)</code> testMatcher.</li><li class="listitem"><p class="simpara"><code class="literal">byCommand(…​)</code>: The value taken from the producer’s response via the provided JSON Path is
|
|
passed as an input to the custom method that you provide. For example,
|
|
<code class="literal">byCommand('foo($it)')</code> results in calling a <code class="literal">foo</code> 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:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: square; "><li class="listitem"><code class="literal">String</code>: If you point to a <code class="literal">String</code> value.</li><li class="listitem"><code class="literal">JSONArray</code>: If you point to a <code class="literal">List</code>.</li><li class="listitem"><code class="literal">Map</code>: If you point to a <code class="literal">Map</code>.</li><li class="listitem"><code class="literal">Number</code>: If you point to <code class="literal">Integer</code>, <code class="literal">Double</code>, or other kind of number.</li><li class="listitem"><code class="literal">Boolean</code>: If you point to a <code class="literal">Boolean</code>.</li></ul></div></li><li class="listitem"><code class="literal">byNull()</code>: The value taken from the response via the provided JSON Path must be null</li></ul></div></li></ul></div><p><b>YAML. </b><span class="emphasis"><em>Please read the Groovy section for detailed explanation of
|
|
what the types mean</em></span></p><p>For YAML the structure of a matcher looks like this</p><pre class="programlisting">- path: $.foo
|
|
type: by_regex
|
|
value: bar
|
|
regexType: as_string</pre><p>Or if you want to use one of the predefined regular expressions
|
|
<code class="literal">[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]</code>:</p><pre class="programlisting">- path: $.foo
|
|
type: by_regex
|
|
predefined: only_alpha_unicode</pre><p>Below you can find the allowed list of `type`s.</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><p class="simpara">For <code class="literal">stubMatchers</code>:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: circle; "><li class="listitem"><code class="literal">by_equality</code></li><li class="listitem"><code class="literal">by_regex</code></li><li class="listitem"><code class="literal">by_date</code></li><li class="listitem"><code class="literal">by_timestamp</code></li><li class="listitem"><code class="literal">by_time</code></li><li class="listitem"><p class="simpara"><code class="literal">by_type</code></p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: square; "><li class="listitem">there are 2 additional fields accepted: <code class="literal">minOccurrence</code> and <code class="literal">maxOccurrence</code>.</li></ul></div></li></ul></div></li><li class="listitem"><p class="simpara">For <code class="literal">testMatchers</code>:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: circle; "><li class="listitem"><code class="literal">by_equality</code></li><li class="listitem"><code class="literal">by_regex</code></li><li class="listitem"><code class="literal">by_date</code></li><li class="listitem"><code class="literal">by_timestamp</code></li><li class="listitem"><code class="literal">by_time</code></li><li class="listitem"><p class="simpara"><code class="literal">by_type</code></p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: square; "><li class="listitem">there are 2 additional fields accepted: <code class="literal">minOccurrence</code> and <code class="literal">maxOccurrence</code>.</li></ul></div></li><li class="listitem"><code class="literal">by_command</code></li><li class="listitem"><code class="literal">by_null</code></li></ul></div></li></ul></div><p>You can also define which type the regular expression corresponds to via the <code class="literal">regexType</code> field. Below you can find the allowed list of regular expression types:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">as_integer</li><li class="listitem">as_double</li><li class="listitem">as_float,</li><li class="listitem">as_long</li><li class="listitem">as_short</li><li class="listitem">as_boolean</li><li class="listitem">as_string</li></ul></div><p>Consider the following example:</p><p><b>Groovy DSL. </b>
|
|
</p><pre class="programlisting">Contract contractDsl = Contract.make {
|
|
request {
|
|
method <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'GET'</span>
|
|
urlPath <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'/get'</span>
|
|
body([
|
|
duck : <span class="hl-number">123</span>,
|
|
alpha : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'abc'</span>,
|
|
number : <span class="hl-number">123</span>,
|
|
aBoolean : true,
|
|
date : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'2017-01-01'</span>,
|
|
dateTime : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'2017-01-01T01:23:45'</span>,
|
|
time : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'01:02:34'</span>,
|
|
valueWithoutAMatcher: <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'foo'</span>,
|
|
valueWithTypeMatch : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'string'</span>,
|
|
key : [
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'complex.key'</span>: <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'foo'</span>
|
|
]
|
|
])
|
|
bodyMatchers {
|
|
jsonPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'$.duck'</span>, byRegex(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"[0-9]{3}"</span>).asInteger())
|
|
jsonPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'$.duck'</span>, byEquality())
|
|
jsonPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'$.alpha'</span>, byRegex(onlyAlphaUnicode()).asString())
|
|
jsonPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'$.alpha'</span>, byEquality())
|
|
jsonPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'$.number'</span>, byRegex(number()).asInteger())
|
|
jsonPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'$.aBoolean'</span>, byRegex(anyBoolean()).asBooleanType())
|
|
jsonPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'$.date'</span>, byDate())
|
|
jsonPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'$.dateTime'</span>, byTimestamp())
|
|
jsonPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'$.time'</span>, byTime())
|
|
jsonPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"\$.['key'].['complex.key']"</span>, byEquality())
|
|
}
|
|
headers {
|
|
contentType(applicationJson())
|
|
}
|
|
}
|
|
response {
|
|
status OK()
|
|
body([
|
|
duck : <span class="hl-number">123</span>,
|
|
alpha : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'abc'</span>,
|
|
number : <span class="hl-number">123</span>,
|
|
positiveInteger : <span class="hl-number">1234567890</span>,
|
|
negativeInteger : -<span class="hl-number">1234567890</span>,
|
|
positiveDecimalNumber: <span class="hl-number">123.4567890</span>,
|
|
negativeDecimalNumber: -<span class="hl-number">123.4567890</span>,
|
|
aBoolean : true,
|
|
date : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'2017-01-01'</span>,
|
|
dateTime : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'2017-01-01T01:23:45'</span>,
|
|
time : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"01:02:34"</span>,
|
|
valueWithoutAMatcher : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'foo'</span>,
|
|
valueWithTypeMatch : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'string'</span>,
|
|
valueWithMin : [
|
|
<span class="hl-number">1</span>, <span class="hl-number">2</span>, <span class="hl-number">3</span>
|
|
],
|
|
valueWithMax : [
|
|
<span class="hl-number">1</span>, <span class="hl-number">2</span>, <span class="hl-number">3</span>
|
|
],
|
|
valueWithMinMax : [
|
|
<span class="hl-number">1</span>, <span class="hl-number">2</span>, <span class="hl-number">3</span>
|
|
],
|
|
valueWithMinEmpty : [],
|
|
valueWithMaxEmpty : [],
|
|
key : [
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'complex.key'</span>: <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'foo'</span>
|
|
],
|
|
nullValue : null
|
|
])
|
|
bodyMatchers {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// asserts the jsonpath value against manual regex</span>
|
|
jsonPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'$.duck'</span>, byRegex(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"[0-9]{3}"</span>).asInteger())
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// asserts the jsonpath value against the provided value</span>
|
|
jsonPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'$.duck'</span>, byEquality())
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// asserts the jsonpath value against some default regex</span>
|
|
jsonPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'$.alpha'</span>, byRegex(onlyAlphaUnicode()).asString())
|
|
jsonPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'$.alpha'</span>, byEquality())
|
|
jsonPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'$.number'</span>, byRegex(number()).asInteger())
|
|
jsonPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'$.positiveInteger'</span>, byRegex(anInteger()).asInteger())
|
|
jsonPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'$.negativeInteger'</span>, byRegex(anInteger()).asInteger())
|
|
jsonPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'$.positiveDecimalNumber'</span>, byRegex(aDouble()).asDouble())
|
|
jsonPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'$.negativeDecimalNumber'</span>, byRegex(aDouble()).asDouble())
|
|
jsonPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'$.aBoolean'</span>, byRegex(anyBoolean()).asBooleanType())
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// asserts vs inbuilt time related regex</span>
|
|
jsonPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'$.date'</span>, byDate())
|
|
jsonPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'$.dateTime'</span>, byTimestamp())
|
|
jsonPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'$.time'</span>, byTime())
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// asserts that the resulting type is the same as in response body</span>
|
|
jsonPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'$.valueWithTypeMatch'</span>, byType())
|
|
jsonPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'$.valueWithMin'</span>, byType {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// results in verification of size of array (min 1)</span>
|
|
minOccurrence(<span class="hl-number">1</span>)
|
|
})
|
|
jsonPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'$.valueWithMax'</span>, byType {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// results in verification of size of array (max 3)</span>
|
|
maxOccurrence(<span class="hl-number">3</span>)
|
|
})
|
|
jsonPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'$.valueWithMinMax'</span>, byType {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// results in verification of size of array (min 1 & max 3)</span>
|
|
minOccurrence(<span class="hl-number">1</span>)
|
|
maxOccurrence(<span class="hl-number">3</span>)
|
|
})
|
|
jsonPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'$.valueWithMinEmpty'</span>, byType {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// results in verification of size of array (min 0)</span>
|
|
minOccurrence(<span class="hl-number">0</span>)
|
|
})
|
|
jsonPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'$.valueWithMaxEmpty'</span>, byType {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// results in verification of size of array (max 0)</span>
|
|
maxOccurrence(<span class="hl-number">0</span>)
|
|
})
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// will execute a method `assertThatValueIsANumber`</span>
|
|
jsonPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'$.duck'</span>, byCommand(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'assertThatValueIsANumber($it)'</span>))
|
|
jsonPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"\$.['key'].['complex.key']"</span>, byEquality())
|
|
jsonPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'$.nullValue'</span>, byNull())
|
|
}
|
|
headers {
|
|
contentType(applicationJson())
|
|
header(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'Some-Header'</span>, $(c(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'someValue'</span>), p(regex(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'[a-zA-Z]{9}'</span>))))
|
|
}
|
|
}
|
|
}</pre><p>
|
|
</p><p><b>YAML. </b>
|
|
</p><pre class="programlisting">request:
|
|
method: GET
|
|
urlPath: /get/1
|
|
headers:
|
|
Content-Type: application/json
|
|
cookies:
|
|
foo: 2
|
|
bar: 3
|
|
queryParameters:
|
|
limit: 10
|
|
offset: 20
|
|
filter: 'email'
|
|
sort: name
|
|
search: 55
|
|
age: 99
|
|
name: John.Doe
|
|
email: 'bob@email.com'
|
|
body:
|
|
duck: 123
|
|
alpha: "abc"
|
|
number: 123
|
|
aBoolean: true
|
|
date: "2017-01-01"
|
|
dateTime: "2017-01-01T01:23:45"
|
|
time: "01:02:34"
|
|
valueWithoutAMatcher: "foo"
|
|
valueWithTypeMatch: "string"
|
|
key:
|
|
"complex.key": 'foo'
|
|
nullValue: null
|
|
valueWithMin:
|
|
- 1
|
|
- 2
|
|
- 3
|
|
valueWithMax:
|
|
- 1
|
|
- 2
|
|
- 3
|
|
valueWithMinMax:
|
|
- 1
|
|
- 2
|
|
- 3
|
|
valueWithMinEmpty: []
|
|
valueWithMaxEmpty: []
|
|
matchers:
|
|
url:
|
|
regex: /get/[0-9]
|
|
# predefined:
|
|
# execute a method
|
|
#command: 'equals($it)'
|
|
queryParameters:
|
|
- key: limit
|
|
type: equal_to
|
|
value: 20
|
|
- key: offset
|
|
type: containing
|
|
value: 20
|
|
- key: sort
|
|
type: equal_to
|
|
value: name
|
|
- key: search
|
|
type: not_matching
|
|
value: '^[0-9]{2}$'
|
|
- key: age
|
|
type: not_matching
|
|
value: '^\\w*$'
|
|
- key: name
|
|
type: matching
|
|
value: 'John.*'
|
|
- key: hello
|
|
type: absent
|
|
cookies:
|
|
- key: foo
|
|
regex: '[0-9]'
|
|
- key: bar
|
|
command: 'equals($it)'
|
|
headers:
|
|
- key: Content-Type
|
|
regex: "application/json.*"
|
|
body:
|
|
- path: $.duck
|
|
type: by_regex
|
|
value: "[0-9]{3}"
|
|
- path: $.duck
|
|
type: by_equality
|
|
- path: $.alpha
|
|
type: by_regex
|
|
predefined: only_alpha_unicode
|
|
- path: $.alpha
|
|
type: by_equality
|
|
- path: $.number
|
|
type: by_regex
|
|
predefined: number
|
|
- path: $.aBoolean
|
|
type: by_regex
|
|
predefined: any_boolean
|
|
- path: $.date
|
|
type: by_date
|
|
- path: $.dateTime
|
|
type: by_timestamp
|
|
- path: $.time
|
|
type: by_time
|
|
- path: "$.['key'].['complex.key']"
|
|
type: by_equality
|
|
- path: $.nullvalue
|
|
type: by_null
|
|
- path: $.valueWithMin
|
|
type: by_type
|
|
minOccurrence: 1
|
|
- path: $.valueWithMax
|
|
type: by_type
|
|
maxOccurrence: 3
|
|
- path: $.valueWithMinMax
|
|
type: by_type
|
|
minOccurrence: 1
|
|
maxOccurrence: 3
|
|
response:
|
|
status: 200
|
|
cookies:
|
|
foo: 1
|
|
bar: 2
|
|
body:
|
|
duck: 123
|
|
alpha: "abc"
|
|
number: 123
|
|
aBoolean: true
|
|
date: "2017-01-01"
|
|
dateTime: "2017-01-01T01:23:45"
|
|
time: "01:02:34"
|
|
valueWithoutAMatcher: "foo"
|
|
valueWithTypeMatch: "string"
|
|
valueWithMin:
|
|
- 1
|
|
- 2
|
|
- 3
|
|
valueWithMax:
|
|
- 1
|
|
- 2
|
|
- 3
|
|
valueWithMinMax:
|
|
- 1
|
|
- 2
|
|
- 3
|
|
valueWithMinEmpty: []
|
|
valueWithMaxEmpty: []
|
|
key:
|
|
'complex.key': 'foo'
|
|
nulValue: null
|
|
matchers:
|
|
headers:
|
|
- key: Content-Type
|
|
regex: "application/json.*"
|
|
cookies:
|
|
- key: foo
|
|
regex: '[0-9]'
|
|
- key: bar
|
|
command: 'equals($it)'
|
|
body:
|
|
- path: $.duck
|
|
type: by_regex
|
|
value: "[0-9]{3}"
|
|
- path: $.duck
|
|
type: by_equality
|
|
- path: $.alpha
|
|
type: by_regex
|
|
predefined: only_alpha_unicode
|
|
- path: $.alpha
|
|
type: by_equality
|
|
- path: $.number
|
|
type: by_regex
|
|
predefined: number
|
|
- path: $.aBoolean
|
|
type: by_regex
|
|
predefined: any_boolean
|
|
- path: $.date
|
|
type: by_date
|
|
- path: $.dateTime
|
|
type: by_timestamp
|
|
- path: $.time
|
|
type: by_time
|
|
- path: $.valueWithTypeMatch
|
|
type: by_type
|
|
- path: $.valueWithMin
|
|
type: by_type
|
|
minOccurrence: 1
|
|
- path: $.valueWithMax
|
|
type: by_type
|
|
maxOccurrence: 3
|
|
- path: $.valueWithMinMax
|
|
type: by_type
|
|
minOccurrence: 1
|
|
maxOccurrence: 3
|
|
- path: $.valueWithMinEmpty
|
|
type: by_type
|
|
minOccurrence: 0
|
|
- path: $.valueWithMaxEmpty
|
|
type: by_type
|
|
maxOccurrence: 0
|
|
- path: $.duck
|
|
type: by_command
|
|
value: assertThatValueIsANumber($it)
|
|
- path: $.nullValue
|
|
type: by_null
|
|
value: null
|
|
headers:
|
|
Content-Type: application/json</pre><p>
|
|
</p><p>In the preceding example, you can see the dynamic portions of the contract in the
|
|
<code class="literal">matchers</code> sections. For the request part, you can see that, for all fields but
|
|
<code class="literal">valueWithoutAMatcher</code>, the values of the regular expressions that the stub should
|
|
contain are explicitly set. For the <code class="literal">valueWithoutAMatcher</code>, the verification takes place
|
|
in the same way as without the use of matchers. In that case, the test performs an
|
|
equality check.</p><p>For the response side in the <code class="literal">bodyMatchers</code> section, we define the dynamic parts in a
|
|
similar manner. The only difference is that the <code class="literal">byType</code> 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):</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">For <code class="literal">$.valueWithTypeMatch</code>, the engine checks whether the type is the same.</li><li class="listitem">For <code class="literal">$.valueWithMin</code>, the engine check the type and asserts whether the size is greater
|
|
than or equal to the minimum occurrence.</li><li class="listitem">For <code class="literal">$.valueWithMax</code>, the engine checks the type and asserts whether the size is
|
|
smaller than or equal to the maximum occurrence.</li><li class="listitem">For <code class="literal">$.valueWithMinMax</code>, the engine checks the type and asserts whether the size is
|
|
between the min and maximum occurrence.</li></ul></div><p>The resulting test would resemble the following example (note that an <code class="literal">and</code> section
|
|
separates the autogenerated assertions and the assertion from matchers):</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// given:</span>
|
|
MockMvcRequestSpecification request = given()
|
|
.header(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"Content-Type"</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"application/json"</span>)
|
|
.body(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"{\"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\"}}"</span>);
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// when:</span>
|
|
ResponseOptions response = given().spec(request)
|
|
.get(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"/get"</span>);
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// then:</span>
|
|
assertThat(response.statusCode()).isEqualTo(<span class="hl-number">200</span>);
|
|
assertThat(response.header(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"Content-Type"</span>)).matches(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"application/json.*"</span>);
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// and:</span>
|
|
DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());
|
|
assertThatJson(parsedJson).field(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"['valueWithoutAMatcher']"</span>).isEqualTo(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"foo"</span>);
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// and:</span>
|
|
assertThat(parsedJson.read(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$.duck"</span>, String.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>)).matches(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"[0-9]{3}"</span>);
|
|
assertThat(parsedJson.read(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$.duck"</span>, Integer.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>)).isEqualTo(<span class="hl-number">123</span>);
|
|
assertThat(parsedJson.read(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$.alpha"</span>, String.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>)).matches(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"[\\p{L}]*"</span>);
|
|
assertThat(parsedJson.read(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$.alpha"</span>, String.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>)).isEqualTo(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"abc"</span>);
|
|
assertThat(parsedJson.read(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$.number"</span>, String.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>)).matches(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"-?(\\d*\\.\\d+|\\d+)"</span>);
|
|
assertThat(parsedJson.read(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$.aBoolean"</span>, String.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>)).matches(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"(true|false)"</span>);
|
|
assertThat(parsedJson.read(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$.date"</span>, String.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>)).matches(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"(\\d\\d\\d\\d)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])"</span>);
|
|
assertThat(parsedJson.read(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$.dateTime"</span>, String.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>)).matches(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"([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])"</span>);
|
|
assertThat(parsedJson.read(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$.time"</span>, String.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>)).matches(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])"</span>);
|
|
assertThat((Object) parsedJson.read(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$.valueWithTypeMatch"</span>)).isInstanceOf(java.lang.String.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>);
|
|
assertThat((Object) parsedJson.read(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$.valueWithMin"</span>)).isInstanceOf(java.util.List.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>);
|
|
assertThat((java.lang.Iterable) parsedJson.read(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$.valueWithMin"</span>, java.util.Collection.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>)).as(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$.valueWithMin"</span>).hasSizeGreaterThanOrEqualTo(<span class="hl-number">1</span>);
|
|
assertThat((Object) parsedJson.read(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$.valueWithMax"</span>)).isInstanceOf(java.util.List.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>);
|
|
assertThat((java.lang.Iterable) parsedJson.read(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$.valueWithMax"</span>, java.util.Collection.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>)).as(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$.valueWithMax"</span>).hasSizeLessThanOrEqualTo(<span class="hl-number">3</span>);
|
|
assertThat((Object) parsedJson.read(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$.valueWithMinMax"</span>)).isInstanceOf(java.util.List.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>);
|
|
assertThat((java.lang.Iterable) parsedJson.read(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$.valueWithMinMax"</span>, java.util.Collection.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>)).as(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$.valueWithMinMax"</span>).hasSizeBetween(<span class="hl-number">1</span>, <span class="hl-number">3</span>);
|
|
assertThat((Object) parsedJson.read(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$.valueWithMinEmpty"</span>)).isInstanceOf(java.util.List.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>);
|
|
assertThat((java.lang.Iterable) parsedJson.read(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$.valueWithMinEmpty"</span>, java.util.Collection.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>)).as(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$.valueWithMinEmpty"</span>).hasSizeGreaterThanOrEqualTo(<span class="hl-number">0</span>);
|
|
assertThat((Object) parsedJson.read(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$.valueWithMaxEmpty"</span>)).isInstanceOf(java.util.List.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>);
|
|
assertThat((java.lang.Iterable) parsedJson.read(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$.valueWithMaxEmpty"</span>, java.util.Collection.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>)).as(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$.valueWithMaxEmpty"</span>).hasSizeLessThanOrEqualTo(<span class="hl-number">0</span>);
|
|
assertThatValueIsANumber(parsedJson.read(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$.duck"</span>));
|
|
assertThat(parsedJson.read(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$.['key'].['complex.key']"</span>, String.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>)).isEqualTo(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"foo"</span>);</pre><div class="important" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Important"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Important]" src="images/important.png"></td><th align="left">Important</th></tr><tr><td align="left" valign="top"><p>Notice that, for the <code class="literal">byCommand</code> method, the example calls the
|
|
<code class="literal">assertThatValueIsANumber</code>. This method must be defined in the test base class or be
|
|
statically imported to your tests. Notice that the <code class="literal">byCommand</code> call was converted to
|
|
<code class="literal">assertThatValueIsANumber(parsedJson.read("$.duck"));</code>. That means that the engine took
|
|
the method name and passed the proper JSON path as a parameter to it.</p></td></tr></table></div><p>The resulting WireMock stub is in the following example:</p><pre class="programlisting"> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">''</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'
|
|
</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"request"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"urlPath"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"/get"</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">,</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"method"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"POST"</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">,</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"headers"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"Content-Type"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"matches"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"application/json.*"</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">}</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">},</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"bodyPatterns"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">[</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"matchesJsonPath"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$.['list'].['some'].['nested'][?(@.['anothervalue'] == 4)]"</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">},</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"matchesJsonPath"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$[?(@.['valueWithoutAMatcher'] == 'foo')]"</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">},</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"matchesJsonPath"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$[?(@.['valueWithTypeMatch'] == 'string')]"</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">},</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"matchesJsonPath"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$.['list'].['someother'].['nested'][?(@.['json'] == 'with value')]"</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">},</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"matchesJsonPath"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$.['list'].['someother'].['nested'][?(@.['anothervalue'] == 4)]"</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">},</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"matchesJsonPath"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$[?(@.duck =~ /([0-9]{3})/)]"</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">},</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"matchesJsonPath"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$[?(@.duck == 123)]"</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">},</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"matchesJsonPath"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$[?(@.alpha =~ /([\\\\p{L}]*)/)]"</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">},</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"matchesJsonPath"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$[?(@.alpha == 'abc')]"</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">},</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"matchesJsonPath"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$[?(@.number =~ /(-?(\\\\d*\\\\.\\\\d+|\\\\d+))/)]"</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">},</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"matchesJsonPath"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$[?(@.aBoolean =~ /((true|false))/)]"</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">},</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"matchesJsonPath"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$[?(@.date =~ /((\\\\d\\\\d\\\\d\\\\d)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01]))/)]"</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">},</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"matchesJsonPath"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$[?(@.dateTime =~ /(([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9]))/)]"</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">},</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"matchesJsonPath"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$[?(@.time =~ /((2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9]))/)]"</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">},</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"matchesJsonPath"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$.list.some.nested[?(@.json =~ /(.*)/)]"</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">},</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"matchesJsonPath"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$[?(@.valueWithMin.size() >= 1)]"</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">},</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"matchesJsonPath"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$[?(@.valueWithMax.size() <= 3)]"</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">},</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"matchesJsonPath"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$[?(@.valueWithMinMax.size() >= 1 && @.valueWithMinMax.size() <= 3)]"</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">},</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"matchesJsonPath"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$[?(@.valueWithOccurrence.size() >= 4 && @.valueWithOccurrence.size() <= 4)]"</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">}</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">]</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">},</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"response"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"status"</span> : <span class="hl-number">200</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">,</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"body"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"{\\"</span>duck\\<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">":123,\\"</span>alpha\\<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">":\\"</span>abc\\<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">",\\"</span>number\\<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">":123,\\"</span>aBoolean\\<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">":true,\\"</span>date\\<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">":\\"</span><span class="hl-number">2017</span>-<span class="hl-number">01</span>-<span class="hl-number">01</span>\\<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">",\\"</span>dateTime\\<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">":\\"</span><span class="hl-number">2017</span>-<span class="hl-number">01</span>-<span class="hl-number">01</span>T01:<span class="hl-number">23</span>:<span class="hl-number">45</span>\\<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">",\\"</span>time\\<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">":\\"</span><span class="hl-number">01</span>:<span class="hl-number">02</span>:<span class="hl-number">34</span>\\<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">",\\"</span>valueWithoutAMatcher\\<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">":\\"</span>foo\\<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">",\\"</span>valueWithTypeMatch\\<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">":\\"</span>string\\<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">",\\"</span>valueWithMin\\<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">":[1,2,3],\\"</span>valueWithMax\\<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">":[1,2,3],\\"</span>valueWithMinMax\\<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">":[1,2,3],\\"</span>valueWithOccurrence\\<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">":[1,2,3,4]}"</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">,</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"headers"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">{</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"Content-Type"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"application/json"</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">},</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"transformers"</span> : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">[</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"response-template"</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">]</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">}</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">}</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">''</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'</span></pre><div class="important" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Important"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Important]" src="images/important.png"></td><th align="left">Important</th></tr><tr><td align="left" valign="top"><p>If you use a <code class="literal">matcher</code>, then the part of the request and response that the
|
|
<code class="literal">matcher</code> addresses with the JSON Path gets removed from the assertion. In the case of
|
|
verifying a collection, you must create matchers for <span class="strong"><strong>all</strong></span> the elements of the
|
|
collection.</p></td></tr></table></div><p>Consider the following example:</p><pre class="programlisting">Contract.make {
|
|
request {
|
|
method <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'GET'</span>
|
|
url(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"/foo"</span>)
|
|
}
|
|
response {
|
|
status OK()
|
|
body(events: [[
|
|
operation : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'EXPORT'</span>,
|
|
eventId : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'16f1ed75-0bcc-4f0d-a04d-3121798faf99'</span>,
|
|
status : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'OK'</span>
|
|
], [
|
|
operation : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'INPUT_PROCESSING'</span>,
|
|
eventId : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'3bb4ac82-6652-462f-b6d1-75e424a0024a'</span>,
|
|
status : <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'OK'</span>
|
|
]
|
|
]
|
|
)
|
|
bodyMatchers {
|
|
jsonPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'$.events[0].operation'</span>, byRegex(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'.+'</span>))
|
|
jsonPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'$.events[0].eventId'</span>, byRegex(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'^([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})$'</span>))
|
|
jsonPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'$.events[0].status'</span>, byRegex(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'.+'</span>))
|
|
}
|
|
}
|
|
}</pre><p>The preceding code leads to creating the following test (the code block shows only the assertion section):</p><pre class="programlisting">and:
|
|
DocumentContext parsedJson = JsonPath.parse(response.body.asString())
|
|
assertThatJson(parsedJson).array(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"['events']"</span>).contains(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"['eventId']"</span>).isEqualTo(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"16f1ed75-0bcc-4f0d-a04d-3121798faf99"</span>)
|
|
assertThatJson(parsedJson).array(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"['events']"</span>).contains(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"['operation']"</span>).isEqualTo(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"EXPORT"</span>)
|
|
assertThatJson(parsedJson).array(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"['events']"</span>).contains(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"['operation']"</span>).isEqualTo(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"INPUT_PROCESSING"</span>)
|
|
assertThatJson(parsedJson).array(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"['events']"</span>).contains(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"['eventId']"</span>).isEqualTo(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"3bb4ac82-6652-462f-b6d1-75e424a0024a"</span>)
|
|
assertThatJson(parsedJson).array(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"['events']"</span>).contains(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"['status']"</span>).isEqualTo(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"OK"</span>)
|
|
and:
|
|
assertThat(parsedJson.read(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"\$.events[0].operation"</span>, String.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>)).matches(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">".+"</span>)
|
|
assertThat(parsedJson.read(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"\$.events[0].eventId"</span>, String.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>)).matches(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"^([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})\$"</span>)
|
|
assertThat(parsedJson.read(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"\$.events[0].status"</span>, String.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>)).matches(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">".+"</span>)</pre><p>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 <code class="literal">$.events</code>
|
|
collection and assert it with the <code class="literal">byCommand(…​)</code> method.</p></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_jax_rs_support" href="#_jax_rs_support"></a>94.6 JAX-RS Support</h2></div></div></div><p>The Spring Cloud Contract Verifier supports the JAX-RS 2 Client API. The base class needs
|
|
to define <code class="literal">protected WebTarget webTarget</code> 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 <code class="literal">application/octet-stream</code> gets used.</p><p>In order to use JAX-RS mode, use the following settings:</p><pre class="programlisting">testMode == <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'JAXRSCLIENT'</span></pre><p>The following example shows a generated test API:</p><pre class="programlisting"> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">''</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'
|
|
</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// when:</span>
|
|
Response response = webTarget
|
|
.path(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"/users"</span>)
|
|
.queryParam(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"limit"</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"10"</span>)
|
|
.queryParam(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"offset"</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"20"</span>)
|
|
.queryParam(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"filter"</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"email"</span>)
|
|
.queryParam(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"sort"</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"name"</span>)
|
|
.queryParam(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"search"</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"55"</span>)
|
|
.queryParam(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"age"</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"99"</span>)
|
|
.queryParam(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"name"</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"Denis.Stepanov"</span>)
|
|
.queryParam(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"email"</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"bob@email.com"</span>)
|
|
.request()
|
|
.method(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"GET"</span>);
|
|
|
|
String responseAsString = response.readEntity(String.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>);
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// then:</span>
|
|
assertThat(response.getStatus()).isEqualTo(<span class="hl-number">200</span>);
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// and:</span>
|
|
DocumentContext parsedJson = JsonPath.parse(responseAsString);
|
|
assertThatJson(parsedJson).field(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"['property1']"</span>).isEqualTo(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"a"</span>);
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">''</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'</span></pre></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_async_support" href="#_async_support"></a>94.7 Async Support</h2></div></div></div><p>If you’re using asynchronous communication on the server side (your controllers are
|
|
returning <code class="literal">Callable</code>, <code class="literal">DeferredResult</code>, and so on), then, inside your contract, you must
|
|
provide an <code class="literal">async()</code> method in the <code class="literal">response</code> section. The following code shows an example:</p><p><b>Groovy DSL. </b>
|
|
</p><pre class="programlisting">org.springframework.cloud.contract.spec.Contract.make {
|
|
request {
|
|
method GET()
|
|
url <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'/get'</span>
|
|
}
|
|
response {
|
|
status OK()
|
|
body <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'Passed'</span>
|
|
async()
|
|
}
|
|
}</pre><p>
|
|
</p><p><b>YAML. </b>
|
|
</p><pre class="programlisting">response:
|
|
async: true</pre><p>
|
|
</p><p>You can also use the <code class="literal">fixedDelayMilliseconds</code> method / property to add delay to your stubs.</p><p><b>Groovy DSL. </b>
|
|
</p><pre class="programlisting">org.springframework.cloud.contract.spec.Contract.make {
|
|
request {
|
|
method GET()
|
|
url <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'/get'</span>
|
|
}
|
|
response {
|
|
status <span class="hl-number">200</span>
|
|
body <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'Passed'</span>
|
|
fixedDelayMilliseconds <span class="hl-number">1000</span>
|
|
}
|
|
}</pre><p>
|
|
</p><p><b>YAML. </b>
|
|
</p><pre class="programlisting">response:
|
|
fixedDelayMilliseconds: 1000</pre><p>
|
|
</p></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_working_with_context_paths" href="#_working_with_context_paths"></a>94.8 Working with Context Paths</h2></div></div></div><p>Spring Cloud Contract supports context paths.</p><div class="important" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Important"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Important]" src="images/important.png"></td><th align="left">Important</th></tr><tr><td align="left" valign="top"><p>The only change needed to fully support context paths is the switch on the
|
|
<span class="strong"><strong>PRODUCER</strong></span> side. Also, the autogenerated tests must use <span class="strong"><strong>EXPLICIT</strong></span> mode. The consumer
|
|
side remains untouched. In order for the generated test to pass, you must use <span class="strong"><strong>EXPLICIT</strong></span>
|
|
mode.</p></td></tr></table></div><p class="primary"><b>Maven. </b>
|
|
</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><plugin></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><groupId></span>org.springframework.cloud<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></groupId></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><artifactId></span>spring-cloud-contract-maven-plugin<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></artifactId></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><version></span>${spring-cloud-contract.version}<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></version></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><extensions></span>true<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></extensions></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><configuration></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><testMode></span>EXPLICIT<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></testMode></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></configuration></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></plugin></span></pre><p class="primary">
|
|
</p><p class="secondary"><b>Gradle. </b>
|
|
</p><pre class="programlisting">contracts {
|
|
testMode = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'EXPLICIT'</span>
|
|
}</pre><p class="secondary">
|
|
</p><p>That way, you generate a test that <span class="strong"><strong>DOES NOT</strong></span> 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.</p><p>Consider the following contract:</p><pre class="programlisting">org.springframework.cloud.contract.spec.Contract.make {
|
|
request {
|
|
method <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'GET'</span>
|
|
url <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'/my-context-path/url'</span>
|
|
}
|
|
response {
|
|
status OK()
|
|
}
|
|
}</pre><p>The following example shows how to set up a base class and Rest Assured:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> io.restassured.RestAssured;
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> org.junit.Before;
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> org.springframework.boot.web.server.LocalServerPort;
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> org.springframework.boot.test.context.SpringBootTest;
|
|
|
|
<em><span class="hl-annotation" style="color: gray">@SpringBootTest(classes = ContextPathTestingBaseClass.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)</span></em>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span> ContextPathTestingBaseClass {
|
|
|
|
<em><span class="hl-annotation" style="color: gray">@LocalServerPort</span></em> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">int</span> port;
|
|
|
|
<em><span class="hl-annotation" style="color: gray">@Before</span></em>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">void</span> setup() {
|
|
RestAssured.baseURI = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"http://localhost"</span>;
|
|
RestAssured.port = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.port;
|
|
}
|
|
}</pre><p>If you do it this way:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">All of your requests in the autogenerated tests are sent to the real endpoint with your
|
|
context path included (for example, <code class="literal">/my-context-path/url</code>).</li><li class="listitem">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 <code class="literal">/my-context-path/url</code>).</li></ul></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_working_with_webflux" href="#_working_with_webflux"></a>94.9 Working with WebFlux</h2></div></div></div><p>Spring Cloud Contract offers two ways of working with WebFlux.</p><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_webflux_with_webtestclient" href="#_webflux_with_webtestclient"></a>94.9.1 WebFlux with WebTestClient</h3></div></div></div><p>One of them is via the <code class="literal">WebTestClient</code> mode.</p><p class="primary"><b>Maven. </b>
|
|
</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><plugin></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><groupId></span>org.springframework.cloud<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></groupId></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><artifactId></span>spring-cloud-contract-maven-plugin<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></artifactId></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><version></span>${spring-cloud-contract.version}<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></version></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><extensions></span>true<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></extensions></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><configuration></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><testMode></span>WEBTESTCLIENT<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></testMode></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></configuration></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></plugin></span></pre><p class="primary">
|
|
</p><p class="secondary"><b>Gradle. </b>
|
|
</p><pre class="programlisting">contracts {
|
|
testMode = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'WEBTESTCLIENT'</span>
|
|
}</pre><p class="secondary">
|
|
</p><p>The following example shows how to set up a <code class="literal">WebTestClient</code> base class and <code class="literal">RestAssured</code>
|
|
for WebFlux:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> io.restassured.module.webtestclient.RestAssuredWebTestClient;
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> org.junit.Before;
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">abstract</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span> BeerRestBase {
|
|
|
|
<em><span class="hl-annotation" style="color: gray">@Before</span></em>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">void</span> setup() {
|
|
RestAssuredWebTestClient.standaloneSetup(
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> ProducerController(personToCheck -> personToCheck.age >= <span class="hl-number">20</span>));
|
|
}
|
|
}
|
|
}</pre></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_webflux_with_explicit_mode" href="#_webflux_with_explicit_mode"></a>94.9.2 WebFlux with Explicit mode</h3></div></div></div><p>Another way is with the <code class="literal">EXPLICIT</code> mode in your generated tests
|
|
to work with WebFlux.</p><p class="primary"><b>Maven. </b>
|
|
</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><plugin></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><groupId></span>org.springframework.cloud<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></groupId></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><artifactId></span>spring-cloud-contract-maven-plugin<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></artifactId></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><version></span>${spring-cloud-contract.version}<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></version></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><extensions></span>true<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></extensions></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><configuration></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><testMode></span>EXPLICIT<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></testMode></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></configuration></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></plugin></span></pre><p class="primary">
|
|
</p><p class="secondary"><b>Gradle. </b>
|
|
</p><pre class="programlisting">contracts {
|
|
testMode = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'EXPLICIT'</span>
|
|
}</pre><p class="secondary">
|
|
</p><p>The following example shows how to set up a base class and Rest Assured for Web Flux:</p><pre class="programlisting"><em><span class="hl-annotation" style="color: gray">@RunWith(SpringRunner.class)</span></em>
|
|
<em><span class="hl-annotation" style="color: gray">@SpringBootTest(classes = BeerRestBase.Config.class,
|
|
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
|
|
properties = "server.port=0")</span></em>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">abstract</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span> BeerRestBase {
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// your tests go here</span>
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// in this config class you define all controllers and mocked services</span>
|
|
<em><span class="hl-annotation" style="color: gray">@Configuration</span></em>
|
|
<em><span class="hl-annotation" style="color: gray">@EnableAutoConfiguration</span></em>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span> Config {
|
|
|
|
<em><span class="hl-annotation" style="color: gray">@Bean</span></em>
|
|
PersonCheckingService personCheckingService() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> personToCheck -> personToCheck.age >= <span class="hl-number">20</span>;
|
|
}
|
|
|
|
<em><span class="hl-annotation" style="color: gray">@Bean</span></em>
|
|
ProducerController producerController() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> ProducerController(personCheckingService());
|
|
}
|
|
}
|
|
|
|
}</pre></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_xml_support_for_rest" href="#_xml_support_for_rest"></a>94.10 XML Support for REST</h2></div></div></div><p>For REST contracts, we also support XML request and response body.
|
|
The XML body has to be passed within the <code class="literal">body</code> element
|
|
as a <code class="literal">String</code> or <code class="literal">GString</code>. Also body matchers can be provided for
|
|
both request and response. In place of the <code class="literal">jsonPath(…​)</code> method, the <code class="literal">org.springframework.cloud.contract.spec.internal.BodyMatchers.xPath</code>
|
|
method should be used, with the desired <code class="literal">xPath</code> provided as the first argument
|
|
and the appropriate <code class="literal">MatchingType</code> as second. All the body matchers apart from <code class="literal">byType()</code> are supported.</p><p>Here is an example of a Groovy DSL contract with XML response body:</p><pre class="programlisting"> Contract.make {
|
|
request {
|
|
method GET()
|
|
urlPath <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'/get'</span>
|
|
headers {
|
|
contentType(applicationXml())
|
|
}
|
|
}
|
|
response {
|
|
status(OK())
|
|
headers {
|
|
contentType(applicationXml())
|
|
}
|
|
body <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">""</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"
|
|
</span><test>
|
|
<duck type=<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'xtype'</span>><span class="hl-number">123</span></duck>
|
|
<alpha>abc</alpha>
|
|
<list>
|
|
<elem>abc</elem>
|
|
<elem>def</elem>
|
|
<elem>ghi</elem>
|
|
</list>
|
|
<number><span class="hl-number">123</span></number>
|
|
<aBoolean>true</aBoolean>
|
|
<date><span class="hl-number">2017</span>-<span class="hl-number">01</span>-<span class="hl-number">01</span></date>
|
|
<dateTime><span class="hl-number">2017</span>-<span class="hl-number">01</span>-<span class="hl-number">01</span>T01:<span class="hl-number">23</span>:<span class="hl-number">45</span></dateTime>
|
|
<time><span class="hl-number">01</span>:<span class="hl-number">02</span>:<span class="hl-number">34</span></time>
|
|
<valueWithoutAMatcher>foo</valueWithoutAMatcher>
|
|
<key><complex>foo</complex></key>
|
|
</test><span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">""</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"
|
|
</span> bodyMatchers {
|
|
xPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'/test/duck/text()'</span>, byRegex(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"[0-9]{3}"</span>))
|
|
xPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'/test/duck/text()'</span>, byCommand(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'test($it)'</span>))
|
|
xPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'/test/duck/xxx'</span>, byNull())
|
|
xPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'/test/duck/text()'</span>, byEquality())
|
|
xPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'/test/alpha/text()'</span>, byRegex(onlyAlphaUnicode()))
|
|
xPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'/test/alpha/text()'</span>, byEquality())
|
|
xPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'/test/number/text()'</span>, byRegex(number()))
|
|
xPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'/test/date/text()'</span>, byDate())
|
|
xPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'/test/dateTime/text()'</span>, byTimestamp())
|
|
xPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'/test/time/text()'</span>, byTime())
|
|
xPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'/test/*/complex/text()'</span>, byEquality())
|
|
xPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'/test/duck/@type'</span>, byEquality())
|
|
}
|
|
}
|
|
}</pre><p>And below is an example of a YAML contract with XML request and response bodies:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute">include</span>::{verifier_core_path}/src/test/resources/yml/contract_rest_xml.yml</pre><p>Here is an example of an automatically generated test for XML response body:</p><pre class="programlisting"><em><span class="hl-annotation" style="color: gray">@Test</span></em>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">void</span> validate_xmlMatches() <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">throws</span> Exception {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// given:</span>
|
|
MockMvcRequestSpecification request = given()
|
|
.header(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"Content-Type"</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"application/xml"</span>);
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// when:</span>
|
|
ResponseOptions response = given().spec(request).get(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"/get"</span>);
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// then:</span>
|
|
assertThat(response.statusCode()).isEqualTo(<span class="hl-number">200</span>);
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// and:</span>
|
|
DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance()
|
|
.newDocumentBuilder();
|
|
Document parsedXml = documentBuilder.parse(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> InputSource(
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> StringReader(response.getBody().asString())));
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// and:</span>
|
|
assertThat(valueFromXPath(parsedXml, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"/test/list/elem/text()"</span>)).isEqualTo(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"abc"</span>);
|
|
assertThat(valueFromXPath(parsedXml,<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"/test/list/elem[2]/text()"</span>)).isEqualTo(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"def"</span>);
|
|
assertThat(valueFromXPath(parsedXml, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"/test/duck/text()"</span>)).matches(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"[0-9]{3}"</span>);
|
|
assertThat(nodeFromXPath(parsedXml, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"/test/duck/xxx"</span>)).isNull();
|
|
assertThat(valueFromXPath(parsedXml, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"/test/alpha/text()"</span>)).matches(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"[\\p{L}]*"</span>);
|
|
assertThat(valueFromXPath(parsedXml, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"/test/*/complex/text()"</span>)).isEqualTo(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"foo"</span>);
|
|
assertThat(valueFromXPath(parsedXml, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"/test/duck/@type"</span>)).isEqualTo(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"xtype"</span>);
|
|
}</pre></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_messaging_top_level_elements" href="#_messaging_top_level_elements"></a>94.11 Messaging Top-Level Elements</h2></div></div></div><p>The DSL for messaging looks a little bit different than the one that focuses on HTTP. The
|
|
following sections explain the differences:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><a class="xref" href="multi_contract-dsl.html#contract-dsl-output-triggered-method" title="94.11.1 Output Triggered by a Method">Section 94.11.1, “Output Triggered by a Method”</a></li><li class="listitem"><a class="xref" href="multi_contract-dsl.html#contract-dsl-output-triggered-message" title="94.11.2 Output Triggered by a Message">Section 94.11.2, “Output Triggered by a Message”</a></li><li class="listitem"><a class="xref" href="multi_contract-dsl.html#contract-dsl-consumer-producer" title="94.11.3 Consumer/Producer">Section 94.11.3, “Consumer/Producer”</a></li><li class="listitem"><a class="xref" href="multi_contract-dsl.html#contract-dsl-common" title="94.11.4 Common">Section 94.11.4, “Common”</a></li></ul></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="contract-dsl-output-triggered-method" href="#contract-dsl-output-triggered-method"></a>94.11.1 Output Triggered by a Method</h3></div></div></div><p>The output message can be triggered by calling a method (such as a <code class="literal">Scheduler</code> when a was
|
|
started and a message was sent), as shown in the following example:</p><p><b>Groovy DSL. </b>
|
|
</p><pre class="programlisting">def dsl = Contract.make {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// Human readable description</span>
|
|
description <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'Some description'</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// Label by means of which the output message can be triggered</span>
|
|
label <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'some_label'</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// input to the contract</span>
|
|
input {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// the contract will be triggered by a method</span>
|
|
triggeredBy(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'bookReturnedTriggered()'</span>)
|
|
}
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// output message of the contract</span>
|
|
outputMessage {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// destination to which the output message will be sent</span>
|
|
sentTo(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'output'</span>)
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// the body of the output message</span>
|
|
body(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">''</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'{ "bookName" : "foo" }'</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">''</span>)
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// the headers of the output message</span>
|
|
headers {
|
|
header(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'BOOK-NAME'</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'foo'</span>)
|
|
}
|
|
}
|
|
}</pre><p>
|
|
</p><p><b>YAML. </b>
|
|
</p><pre class="programlisting"># Human readable description
|
|
description: Some description
|
|
# Label by means of which the output message can be triggered
|
|
label: some_label
|
|
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:
|
|
BOOK-NAME: foo</pre><p>
|
|
</p><p>In the previous example case, the output message is sent to <code class="literal">output</code> if a method called
|
|
<code class="literal">bookReturnedTriggered</code> is executed. On the message <span class="strong"><strong>publisher’s</strong></span> side, we generate a
|
|
test that calls that method to trigger the message. On the <span class="strong"><strong>consumer</strong></span> side, you can use
|
|
the <code class="literal">some_label</code> to trigger the message.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="contract-dsl-output-triggered-message" href="#contract-dsl-output-triggered-message"></a>94.11.2 Output Triggered by a Message</h3></div></div></div><p>The output message can be triggered by receiving a message, as shown in the following
|
|
example:</p><p><b>Groovy DSL. </b>
|
|
</p><pre class="programlisting">def dsl = Contract.make {
|
|
description <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'Some Description'</span>
|
|
label <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'some_label'</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// input is a message</span>
|
|
input {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// the message was received from this destination</span>
|
|
messageFrom(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'input'</span>)
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// has the following body</span>
|
|
messageBody([
|
|
bookName: <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'foo'</span>
|
|
])
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// and the following headers</span>
|
|
messageHeaders {
|
|
header(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'sample'</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'header'</span>)
|
|
}
|
|
}
|
|
outputMessage {
|
|
sentTo(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'output'</span>)
|
|
body([
|
|
bookName: <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'foo'</span>
|
|
])
|
|
headers {
|
|
header(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'BOOK-NAME'</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'foo'</span>)
|
|
}
|
|
}
|
|
}</pre><p>
|
|
</p><p><b>YAML. </b>
|
|
</p><pre class="programlisting"># Human readable description
|
|
description: Some description
|
|
# Label by means of which the output message can be triggered
|
|
label: some_label
|
|
# input is a message
|
|
input:
|
|
messageFrom: input
|
|
# has the following body
|
|
messageBody:
|
|
bookName: 'foo'
|
|
# and the following headers
|
|
messageHeaders:
|
|
sample: 'header'
|
|
# 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:
|
|
BOOK-NAME: foo</pre><p>
|
|
</p><p>In the preceding example, the output message is sent to <code class="literal">output</code> if a proper message is
|
|
received on the <code class="literal">input</code> destination. On the message <span class="strong"><strong>publisher’s</strong></span> side, the engine
|
|
generates a test that sends the input message to the defined destination. On the
|
|
<span class="strong"><strong>consumer</strong></span> side, you can either send a message to the input destination or use a label
|
|
(<code class="literal">some_label</code> in the example) to trigger the message.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="contract-dsl-consumer-producer" href="#contract-dsl-consumer-producer"></a>94.11.3 Consumer/Producer</h3></div></div></div><div class="important" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Important"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Important]" src="images/important.png"></td><th align="left">Important</th></tr><tr><td align="left" valign="top"><p>This section is valid only for Groovy DSL.</p></td></tr></table></div><p>In HTTP, you have a notion of <code class="literal">client</code>/<code class="literal">stub and `server</code>/<code class="literal">test</code> notation. You can also
|
|
use those paradigms in messaging. In addition, Spring Cloud Contract Verifier also
|
|
provides the <code class="literal">consumer</code> and <code class="literal">producer</code> methods, as presented in the following example
|
|
(note that you can use either <code class="literal">$</code> or <code class="literal">value</code> methods to provide <code class="literal">consumer</code> and <code class="literal">producer</code>
|
|
parts):</p><pre class="programlisting"> Contract.make {
|
|
label <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'some_label'</span>
|
|
input {
|
|
messageFrom value(consumer(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'jms:output'</span>), producer(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'jms:input'</span>))
|
|
messageBody([
|
|
bookName: <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'foo'</span>
|
|
])
|
|
messageHeaders {
|
|
header(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'sample'</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'header'</span>)
|
|
}
|
|
}
|
|
outputMessage {
|
|
sentTo $(consumer(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'jms:input'</span>), producer(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'jms:output'</span>))
|
|
body([
|
|
bookName: <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'foo'</span>
|
|
])
|
|
}
|
|
}</pre></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="contract-dsl-common" href="#contract-dsl-common"></a>94.11.4 Common</h3></div></div></div><p>In the <code class="literal">input</code> or <code class="literal">outputMessage</code> section you can call <code class="literal">assertThat</code> with the name
|
|
of a <code class="literal">method</code> (e.g. <code class="literal">assertThatMessageIsOnTheQueue()</code>) that you have defined in the
|
|
base class or in a static import. Spring Cloud Contract will execute that method
|
|
in the generated test.</p></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_multiple_contracts_in_one_file" href="#_multiple_contracts_in_one_file"></a>94.12 Multiple Contracts in One File</h2></div></div></div><p>You can define multiple contracts in one file. Such a contract might resemble the
|
|
following example:</p><p><b>Groovy DSL. </b>
|
|
</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> org.springframework.cloud.contract.spec.Contract
|
|
|
|
[
|
|
Contract.make {
|
|
name(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"should post a user"</span>)
|
|
request {
|
|
method <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'POST'</span>
|
|
url(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'/users/1'</span>)
|
|
}
|
|
response {
|
|
status OK()
|
|
}
|
|
},
|
|
Contract.make {
|
|
request {
|
|
method <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'POST'</span>
|
|
url(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'/users/2'</span>)
|
|
}
|
|
response {
|
|
status OK()
|
|
}
|
|
}
|
|
]</pre><p>
|
|
</p><p><b>YAML. </b>
|
|
</p><pre class="programlisting">---
|
|
name: should post a user
|
|
request:
|
|
method: POST
|
|
url: /users/1
|
|
response:
|
|
status: 200
|
|
---
|
|
request:
|
|
method: POST
|
|
url: /users/2
|
|
response:
|
|
status: 200
|
|
---
|
|
request:
|
|
method: POST
|
|
url: /users/3
|
|
response:
|
|
status: 200</pre><p>
|
|
</p><p>In the preceding example, one contract has the <code class="literal">name</code> field and the other does not. This
|
|
leads to generation of two tests that look more or less like this:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">package</span> org.springframework.cloud.contract.verifier.tests.com.hello;
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> com.example.TestBase;
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> com.jayway.jsonpath.DocumentContext;
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> com.jayway.jsonpath.JsonPath;
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> com.jayway.restassured.module.mockmvc.specification.MockMvcRequestSpecification;
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> com.jayway.restassured.response.ResponseOptions;
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> org.junit.Test;
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> com.jayway.restassured.module.mockmvc.RestAssuredMockMvc.*;
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> com.toomuchcoding.jsonassert.JsonAssertion.assertThatJson;
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> org.assertj.core.api.Assertions.assertThat;
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span> V1Test <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">extends</span> TestBase {
|
|
|
|
<em><span class="hl-annotation" style="color: gray">@Test</span></em>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">void</span> validate_should_post_a_user() <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">throws</span> Exception {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// given:</span>
|
|
MockMvcRequestSpecification request = given();
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// when:</span>
|
|
ResponseOptions response = given().spec(request)
|
|
.post(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"/users/1"</span>);
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// then:</span>
|
|
assertThat(response.statusCode()).isEqualTo(<span class="hl-number">200</span>);
|
|
}
|
|
|
|
<em><span class="hl-annotation" style="color: gray">@Test</span></em>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">void</span> validate_withList_<span class="hl-number">1</span>() <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">throws</span> Exception {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// given:</span>
|
|
MockMvcRequestSpecification request = given();
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// when:</span>
|
|
ResponseOptions response = given().spec(request)
|
|
.post(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"/users/2"</span>);
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// then:</span>
|
|
assertThat(response.statusCode()).isEqualTo(<span class="hl-number">200</span>);
|
|
}
|
|
|
|
}</pre><p>Notice that, for the contract that has the <code class="literal">name</code> field, the generated test method is named
|
|
<code class="literal">validate_should_post_a_user</code>. For the one that does not have the name, it is called
|
|
<code class="literal">validate_withList_1</code>. It corresponds to the name of the file <code class="literal">WithList.groovy</code> and the
|
|
index of the contract in the list.</p><p>The generated stubs is shown in the following example:</p><pre class="screen">should post a user.json
|
|
1_WithList.json</pre><p>As you can see, the first file got the <code class="literal">name</code> parameter from the contract. The second
|
|
got the name of the contract file (<code class="literal">WithList.groovy</code>) prefixed with the index (in this
|
|
case, the contract had an index of <code class="literal">1</code> in the list of contracts in the file).</p><div class="tip" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Tip"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Tip]" src="images/tip.png"></td><th align="left">Tip</th></tr><tr><td align="left" valign="top"><p>As you can see, it is much better if you name your contracts because doing so makes
|
|
your tests far more meaningful.</p></td></tr></table></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_generating_spring_rest_docs_snippets_from_the_contracts" href="#_generating_spring_rest_docs_snippets_from_the_contracts"></a>94.13 Generating Spring REST Docs snippets from the contracts</h2></div></div></div><p>When you want to include the requests and responses of your API using Spring REST Docs,
|
|
you only need to make some minor changes to your setup if you are using MockMvc and RestAssuredMockMvc.
|
|
Simply include the following dependencies if you haven’t already.</p><p><b>Maven. </b>
|
|
</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><dependency></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><groupId></span>org.springframework.cloud<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></groupId></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><artifactId></span>spring-cloud-starter-contract-verifier<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></artifactId></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><scope></span>test<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></scope></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></dependency></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><dependency></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><groupId></span>org.springframework.restdocs<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></groupId></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><artifactId></span>spring-restdocs-mockmvc<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></artifactId></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><optional></span>true<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></optional></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></dependency></span></pre><p>
|
|
</p><p><b>Gradle. </b>
|
|
</p><pre class="programlisting">testCompile <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'org.springframework.cloud:spring-cloud-starter-contract-verifier'</span>
|
|
testCompile <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'org.springframework.restdocs:spring-restdocs-mockmvc'</span></pre><p>
|
|
</p><p>Next you need to make some changes to your base class like the following example.</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">package</span> com.example.fraud;
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> io.restassured.module.mockmvc.RestAssuredMockMvc;
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> org.junit.Before;
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> org.junit.Rule;
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> org.junit.rules.TestName;
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> org.junit.runner.RunWith;
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> org.springframework.beans.factory.annotation.Autowired;
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> org.springframework.boot.test.context.SpringBootTest;
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> org.springframework.restdocs.JUnitRestDocumentation;
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> org.springframework.test.context.junit4.SpringRunner;
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> org.springframework.web.context.WebApplicationContext;
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
|
|
|
|
<em><span class="hl-annotation" style="color: gray">@RunWith(SpringRunner.class)</span></em>
|
|
<em><span class="hl-annotation" style="color: gray">@SpringBootTest(classes = Application.class)</span></em>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">abstract</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span> FraudBaseWithWebAppSetup {
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">private</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">final</span> String OUTPUT = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"target/generated-snippets"</span>;
|
|
|
|
<em><span class="hl-annotation" style="color: gray">@Rule</span></em>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> JUnitRestDocumentation restDocumentation = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> JUnitRestDocumentation(OUTPUT);
|
|
|
|
<em><span class="hl-annotation" style="color: gray">@Rule</span></em>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> TestName testName = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> TestName();
|
|
|
|
<em><span class="hl-annotation" style="color: gray">@Autowired</span></em>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">private</span> WebApplicationContext context;
|
|
|
|
<em><span class="hl-annotation" style="color: gray">@Before</span></em>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">void</span> setup() {
|
|
RestAssuredMockMvc.mockMvc(MockMvcBuilders.webAppContextSetup(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.context)
|
|
.apply(documentationConfiguration(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.restDocumentation))
|
|
.alwaysDo(document(
|
|
getClass().getSimpleName() + <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"_"</span> + testName.getMethodName()))
|
|
.build());
|
|
}
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">protected</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">void</span> assertThatRejectionReasonIsNull(Object rejectionReason) {
|
|
assert rejectionReason == null;
|
|
}
|
|
|
|
}</pre><p>In case you are using the standalone setup, you can set up RestAssuredMockMvc like this:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">package</span> com.example.fraud;
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> io.restassured.module.mockmvc.RestAssuredMockMvc;
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> org.junit.Before;
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> org.junit.Rule;
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> org.junit.rules.TestName;
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> org.springframework.restdocs.JUnitRestDocumentation;
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">abstract</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span> FraudBaseWithStandaloneSetup {
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">private</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">final</span> String OUTPUT = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"target/generated-snippets"</span>;
|
|
|
|
<em><span class="hl-annotation" style="color: gray">@Rule</span></em>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> JUnitRestDocumentation restDocumentation = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> JUnitRestDocumentation(OUTPUT);
|
|
|
|
<em><span class="hl-annotation" style="color: gray">@Rule</span></em>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> TestName testName = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> TestName();
|
|
|
|
<em><span class="hl-annotation" style="color: gray">@Before</span></em>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">void</span> setup() {
|
|
RestAssuredMockMvc.standaloneSetup(MockMvcBuilders
|
|
.standaloneSetup(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> FraudDetectionController())
|
|
.apply(documentationConfiguration(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.restDocumentation))
|
|
.alwaysDo(document(
|
|
getClass().getSimpleName() + <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"_"</span> + testName.getMethodName())));
|
|
}
|
|
|
|
}</pre><div class="tip" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Tip"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Tip]" src="images/tip.png"></td><th align="left">Tip</th></tr><tr><td align="left" valign="top"><p>You don’t need to specify the output directory for the generated snippets since version 1.2.0.RELEASE of Spring REST Docs.</p></td></tr></table></div></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="multi_stub-runner-for-messaging.html">Prev</a> </td><td width="20%" align="center"><a accesskey="u" href="multi__spring_cloud_contract.html">Up</a></td><td width="40%" align="right"> <a accesskey="n" href="multi__customization.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">93. Stub Runner for Messaging </td><td width="20%" align="center"><a accesskey="h" href="multi_spring-cloud.html">Home</a></td><td width="40%" align="right" valign="top"> 95. Customization</td></tr></table></div></body></html> |