1141 lines
140 KiB
HTML
1141 lines
140 KiB
HTML
<html><head>
|
|
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
|
<title>7. Contract DSL</title><link rel="stylesheet" type="text/css" href="css/manual-multipage.css"><meta name="generator" content="DocBook XSL Stylesheets V1.78.1"><link rel="home" href="multi_spring-cloud-contract.html" title="Spring Cloud Contract"><link rel="up" href="multi_spring-cloud-contract.html" title="Spring Cloud Contract"><link rel="prev" href="multi__stub_runner_for_messaging.html" title="6. Stub Runner for Messaging"><link rel="next" href="multi__customization.html" title="8. 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">7. 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"> </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><h1 class="title"><a name="_contract_dsl" href="#_contract_dsl"></a>7. Contract DSL</h1></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>Remember that inside the contract file you have to provide the fully qualified name to
|
|
the <code class="literal">Contract</code> class and the <code class="literal">make</code> static import i.e. <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><p>Contract DSL is written in Groovy, but don’t be alarmed if you didn’t use Groovy before. Knowledge of the language is not really needed as our DSL uses only
|
|
a tiny subset of it (namely literals, method calls and closures). What’s more the DSL is designed to be programmer-readable without any knowledge of the DSL itself -
|
|
it’s statically typed.</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>Spring Cloud Contract supports defining multiple contracts in a single file!</p></td></tr></table></div><p>The Contract is present in the <code class="literal">spring-cloud-contract-spec</code> module of the Spring Cloud Contract Verifier repository.</p><p>Let’s look at full example of a contract definition.</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 <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/vnd.org.springframework.cloud.contract.verifier.twitter-places-analyzer.v1+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">"created_at"</span>: <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"Sat Jul 26 09:38:57 +0000 2014"</span>,
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"id"</span>: <span class="hl-number">492967299297845248</span>,
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"id_str"</span>: <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"492967299297845248"</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">"place"</span>:
|
|
{
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"attributes"</span>:{},
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"bounding_box"</span>:
|
|
{
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"coordinates"</span>:
|
|
[[
|
|
[-<span class="hl-number">77.119759</span>,<span class="hl-number">38.791645</span>],
|
|
[-<span class="hl-number">76.909393</span>,<span class="hl-number">38.791645</span>],
|
|
[-<span class="hl-number">76.909393</span>,<span class="hl-number">38.995548</span>],
|
|
[-<span class="hl-number">77.119759</span>,<span class="hl-number">38.995548</span>]
|
|
]],
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"type"</span>:<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"Polygon"</span>
|
|
},
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"country"</span>:<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"United States"</span>,
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"country_code"</span>:<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"US"</span>,
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"full_name"</span>:<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"Washington, DC"</span>,
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"id"</span>:<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"01fbe706f872cb32"</span>,
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"name"</span>:<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"Washington"</span>,
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"place_type"</span>:<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"city"</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">"http://api.twitter.com/1/geo/id/01fbe706f872cb32.json"</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 {
|
|
status <span class="hl-number">200</span>
|
|
}
|
|
}</pre><p>Not all features of the DSL are used in example above. If you didn’t find what you are looking for, please check next paragraphs on this page.</p><div class="blockquote"><blockquote class="blockquote"><p>You can easily compile Contracts to WireMock stubs mapping using standalone maven command: <code class="literal">mvn org.springframework.cloud:spring-cloud-contract-maven-plugin:convert</code>.</p></blockquote></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_limitations_2" href="#_limitations_2"></a>7.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 doesn’t support XML properly. 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 the verification of size of JSON arrays is experimental. If you want to turn it on please provide
|
|
the value of a system property <code class="literal">spring.cloud.contract.verifier.assert.size</code> equal to <code class="literal">true</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>Due to the fact that JSON structure can have any form it’s sometimes impossible to parse it properly when using
|
|
the <code class="literal">value(consumer(…​), producer(…​))</code> notation when using that in GString. That’s why we highly recommend using 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>7.2 Common Top-Level elements</h2></div></div></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_description" href="#_description"></a>7.2.1 Description</h3></div></div></div><p>You can add a <code class="literal">description</code> to your contract that is nothing else but an arbitrary text. Example:</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></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_name" href="#_name"></a>7.2.2 Name</h3></div></div></div><p>You can provide a name of your contract. Let’s assume that you’ve provided a name <code class="literal">should register a user</code>.
|
|
If you do this then the name of the autogenerated test will be equal to <code class="literal">validate_should_register_a_user</code>.
|
|
Also the name of the stub will be <code class="literal">should_register_a_user.json</code> in case of a WireMock stub.</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>Please ensure that the name doesn’t contain any characters that will make the generated test
|
|
not possible to compile. Also remember that if you provide the same name for multiple contracts then your
|
|
autogenerated tests will fail to compile and your generated stubs will override each other.</p></td></tr></table></div></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_ignoring_contracts" href="#_ignoring_contracts"></a>7.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 just set the <code class="literal">ignored</code> property on the contract itself:</p><pre class="programlisting">org.springframework.cloud.contract.spec.Contract.make {
|
|
ignored()
|
|
}</pre></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_passing_values_from_files" href="#_passing_values_from_files"></a>7.2.4 Passing values from files</h3></div></div></div><p>Starting with version <code class="literal">1.2.0</code> it’s possible to pass values from files. Let’s assume that we 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>And our contract looks like this:</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 {
|
|
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 <span class="hl-number">200</span>
|
|
body(file(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"response.json"</span>))
|
|
headers {
|
|
contentType(textPlain())
|
|
}
|
|
}
|
|
}</pre><p>and the json files look like this:</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 / stub generation takes place then the contents of the file will be
|
|
passed to the body of request / response. All thanks to the <code class="literal">file(…​)</code> method.
|
|
The argument of that method needs to be a file with location relative to the
|
|
folder in which the contract lays.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_http_top_level_elements" href="#_http_top_level_elements"></a>7.2.5 HTTP Top-Level Elements</h3></div></div></div><p>Following methods can be called in the top-level closure of a contract definition. Request and response are mandatory, priority is optional.</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 {
|
|
<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 {
|
|
<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></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_request" href="#_request"></a>7.3 Request</h2></div></div></div><p>HTTP protocol requires only <span class="strong"><strong>method and address</strong></span> to be specified in a request. The same information is mandatory in request definition of the Contract.</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>
|
|
}
|
|
}</pre><p>It is possible to specify whole <code class="literal">url</code> instead of just path, but <code class="literal">urlPath</code> is the recommended way as it makes the tests <span class="strong"><strong>host-independent</strong></span>.</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>
|
|
}
|
|
}</pre><p>Request may contain <span class="strong"><strong>query parameters</strong></span>, which are specified in a closure nested in a call to <code class="literal">urlPath</code> or <code class="literal">url</code>.</p><pre class="programlisting">org.springframework.cloud.contract.spec.Contract.make {
|
|
request {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">//...</span>
|
|
|
|
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>
|
|
}
|
|
}</pre><p>It may contain additional <span class="strong"><strong>request headers</strong></span>…​</p><pre class="programlisting">org.springframework.cloud.contract.spec.Contract.make {
|
|
request {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">//...</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>
|
|
}
|
|
}</pre><p>…​and a <span class="strong"><strong>request body</strong></span>.</p><pre class="programlisting">org.springframework.cloud.contract.spec.Contract.make {
|
|
request {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">//...</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>
|
|
}
|
|
}</pre><p>Request may contain <span class="strong"><strong>multipart</strong></span> elements. Just call the <code class="literal">multipart()</code> method.</p><pre class="programlisting">org.springframework.cloud.contract.spec.Contract contractDsl = org.springframework.cloud.contract.spec.Contract.make {
|
|
request {
|
|
method <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"PUT"</span>
|
|
url <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"/multipart"</span>
|
|
headers {
|
|
contentType(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'multipart/form-data;boundary=AaB03x'</span>)
|
|
}
|
|
multipart(
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// key (parameter name), value (parameter value) pair</span>
|
|
formParameter: $(c(regex(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'".+"'</span>)), p(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'"formParameterValue"'</span>)),
|
|
someBooleanParameter: $(c(regex(anyBoolean())), p(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'true'</span>)),
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// a named parameter (e.g. with `file` name) that represents file with</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// `name` and `content`. You can also call `named("fileName", "fileContent")`</span>
|
|
file: named(
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// name of the file</span>
|
|
name: $(c(regex(nonEmpty())), p(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'filename.csv'</span>)),
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// content of the file</span>
|
|
content: $(c(regex(nonEmpty())), p(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'file content'</span>)))
|
|
)
|
|
}
|
|
response {
|
|
status <span class="hl-number">200</span>
|
|
}
|
|
}</pre><p>In this example we defined parameters either directly by using the map notation,
|
|
where the value can be a dynamic property (e.g. <code class="literal">formParameter: $(consumer(…​), producer(…​))</code>)
|
|
or by using the <code class="literal">named(…​)</code> method that allows you to 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 2 arguments: e.g. <code class="literal">named("fileName", "fileContent")</code> or
|
|
via a map notation <code class="literal">named(name: "fileName", content: "fileContent")</code>.</p><p>From this contract the generated test will look more or less like this:</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 will look more or less like this:</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-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-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-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-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>7.4 Response</h2></div></div></div><p>Minimal response must contain <span class="strong"><strong>HTTP status code</strong></span>.</p><pre class="programlisting">org.springframework.cloud.contract.spec.Contract.make {
|
|
request {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">//...</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 <span class="hl-number">200</span>
|
|
}
|
|
}</pre><p>Besides status response may contain <span class="strong"><strong>headers</strong></span> and <span class="strong"><strong>body</strong></span>, which are specified the same way as in the request (see previous paragraph).</p></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_dynamic_properties" href="#_dynamic_properties"></a>7.5 Dynamic properties</h2></div></div></div><p>The contract can contain some dynamic properties - timestamps / ids etc. You don’t want to enforce the consumers to stub their
|
|
clocks to always return the same value of time so that it gets matched by the stub. That’s why we allow you to provide the dynamic
|
|
parts in your contracts in two ways. One is to pass them directly in the
|
|
body and one to set them in a separate section called <code class="literal">testMatchers</code> and <code class="literal">stubMatchers</code>.</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>7.5.1 Dynamic properties inside the body</h3></div></div></div><p>You can set the properties inside the body either via the <code class="literal">value</code> method</p><pre class="programlisting">value(consumer(...), producer(...))
|
|
value(c(...), p(...))
|
|
value(stub(...), test(...))
|
|
value(client(...), server(...))</pre><p>or if you’re using the Groovy map notation for body you can use the <code class="literal">$()</code> method</p><pre class="programlisting">$(consumer(...), producer(...))
|
|
$(c(...), p(...))
|
|
$(stub(...), test(...))
|
|
$(client(...), server(...))</pre><p>All of the aforementioned approaches are equal. That means that <code class="literal">stub</code> and <code class="literal">client</code> methods are aliases over the <code class="literal">consumer</code>
|
|
method. Let’s take a closer look at what we can do with those values in the subsequent sections.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_regular_expressions" href="#_regular_expressions"></a>7.5.2 Regular expressions</h3></div></div></div><p>You can use regular expressions to write your requests in Contract DSL. It is particularly useful when you want to indicate that a given response
|
|
should be provided for requests that follow a given pattern. Also, you can use it when you need to use patterns and not exact values both
|
|
for your test and your server side tests.</p><p>Please see the example below:</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 <span class="hl-number">200</span>
|
|
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 using a regular expression. If you do that then automatically we’ll
|
|
provide the generated string that matches the provided regular expression. For 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 <span class="hl-number">200</span>
|
|
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 this example for request and response the opposite side of the communication will have the respective data generated.</p><p>Spring Cloud Contract comes with a series of predefined regular expressions that you can use in your contracts.</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 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+)?'</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 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>))
|
|
}
|
|
|
|
String onlyAlphaUnicode() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> ONLY_ALPHA_UNICODE.pattern()
|
|
}
|
|
|
|
String number() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> NUMBER.pattern()
|
|
}
|
|
|
|
String anyBoolean() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> TRUE_OR_FALSE.pattern()
|
|
}
|
|
|
|
String ipAddress() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> IP_ADDRESS.pattern()
|
|
}
|
|
|
|
String hostname() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> HOSTNAME_PATTERN.pattern()
|
|
}
|
|
|
|
String email() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> EMAIL.pattern()
|
|
}
|
|
|
|
String url() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> URL.pattern()
|
|
}
|
|
|
|
String uuid(){
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> UUID.pattern()
|
|
}
|
|
|
|
String isoDate() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> ANY_DATE.pattern()
|
|
}
|
|
|
|
String isoDateTime() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> ANY_DATE_TIME.pattern()
|
|
}
|
|
|
|
String isoTime() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> ANY_TIME.pattern()
|
|
}
|
|
|
|
String iso8601WithOffset() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> ISO8601_WITH_OFFSET.pattern()
|
|
}
|
|
|
|
String nonEmpty() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> NON_EMPTY.pattern()
|
|
}
|
|
|
|
String nonBlank() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> NON_BLANK.pattern()
|
|
}</pre><p>so in your contract you can use it like this</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></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_passing_optional_parameters" href="#_passing_optional_parameters"></a>7.5.3 Passing optional parameters</h3></div></div></div><p>It is possible to provide optional parameters in your contract. It’s only possible to have optional parameter for the:</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>Example:</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">'http://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 are in fact creating a regular expression that should be present 0 or more times.</p><p>That way for the example above the following test would be generated if you pick Spock:</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":"http://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>and the following stub:</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_server_side" href="#_executing_custom_methods_on_server_side"></a>7.5.4 Executing custom methods on server side</h3></div></div></div><p>It is also possible to define a method call to be executed on the server side during the test. Such a method can be added to the class defined as "baseClassForTests"
|
|
in the configuration. Example:</p><p><span class="strong"><strong>Contract</strong></span></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 <span class="hl-number">200</span>
|
|
}
|
|
}</pre><p><span class="strong"><strong>Base class</strong></span></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 can’t use both a String and <code class="literal">execute</code> to perform concatenation. E.g. calling
|
|
<code class="literal">header('Authorization', 'Bearer ' + execute('authToken()'))</code> will lead to improper results.
|
|
To make this work just call <code class="literal">header('Authorization', execute('authToken()'))</code> and ensure that
|
|
the <code class="literal">authToken()</code> method returns everything that you need.</p></td></tr></table></div><p>The type of the object read from the JSON can be one of the followings 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 a JSON</li><li class="listitem"><code class="literal">JSONArray</code> if you point to a <code class="literal">List</code> in a JSON</li><li class="listitem"><code class="literal">Map</code> if you point to a <code class="literal">Map</code> in a JSON</li><li class="listitem">proper <code class="literal">Number</code> if you point to <code class="literal">Integer</code>, <code class="literal">Double</code> etc. in a JSON</li><li class="listitem"><code class="literal">Boolean</code> if you point to a <code class="literal">Boolean</code> in a 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 have to provide both the consumer and the producer side
|
|
and the <code class="literal">execute</code> part can be applied for the whole body. Not for parts of it!</p></td></tr></table></div><p>Example:</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 <span class="hl-number">200</span>
|
|
}
|
|
}</pre><p>This will result in calling the <code class="literal">hashCode()</code> method in the request body.
|
|
It would more or less like this:</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_request_from_response" href="#_referencing_request_from_response"></a>7.5.5 Referencing request from 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.
|
|
In order to do this you can profit from the <code class="literal">fromRequest()</code> method that allows you to 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> - return the request URL and query parameters</li><li class="listitem"><code class="literal">fromRequest().query(String key)</code> - return the first query parameter with a given name</li><li class="listitem"><code class="literal">fromRequest().query(String key, int index)</code> - return the nth query parameter with a given name</li><li class="listitem"><code class="literal">fromRequest().path()</code> - return the full path</li><li class="listitem"><code class="literal">fromRequest().path(int index)</code> - return the nth path element</li><li class="listitem"><code class="literal">fromRequest().header(String key)</code> - return the first header with a given name</li><li class="listitem"><code class="literal">fromRequest().header(String key, int index)</code> - return the nth header with a given name</li><li class="listitem"><code class="literal">fromRequest().body()</code> - return the full request body</li><li class="listitem"><code class="literal">fromRequest().body(String jsonPath)</code> - return the element from the request that matches the JSON Path</li></ul></div><p>Let’s take a look at the following contract</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">'/api/v1/xxxx'</span>) {
|
|
queryParameters {
|
|
parameter(<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>)
|
|
parameter(<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>)
|
|
}
|
|
}
|
|
headers {
|
|
header(authorization(), <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"secret"</span>)
|
|
header(authorization(), <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"secret2"</span>)
|
|
}
|
|
body(foo: <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"bar"</span>, baz: <span class="hl-number">5</span>)
|
|
}
|
|
response {
|
|
status <span class="hl-number">200</span>
|
|
headers {
|
|
header(authorization(), <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"foo ${fromRequest().header(authorization())} bar"</span>)
|
|
}
|
|
body(
|
|
url: fromRequest().url(),
|
|
path: fromRequest().path(),
|
|
pathIndex: fromRequest().path(<span class="hl-number">1</span>),
|
|
param: fromRequest().query(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"foo"</span>),
|
|
paramIndex: fromRequest().query(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"foo"</span>, <span class="hl-number">1</span>),
|
|
authorization: fromRequest().header(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"Authorization"</span>),
|
|
authorization2: fromRequest().header(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"Authorization"</span>, <span class="hl-number">1</span>),
|
|
fullBody: fromRequest().body(),
|
|
responseFoo: fromRequest().body(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'$.foo'</span>),
|
|
responseBaz: fromRequest().body(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'$.baz'</span>),
|
|
responseBaz2: <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"Bla bla ${fromRequest().body('$.foo')} bla bla"</span>
|
|
)
|
|
}
|
|
}</pre><p>Running a JUnit test generation will lead in creation of a test looking more or less like this</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 will look more or less like this:</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>So sending a request as the one presented in the <code class="literal">request</code> part of the contract will lead 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 will work only with WireMock having version greater or equal to 2.5.1. We’re using WireMock’s
|
|
<code class="literal">response-template</code> response transformer. It’s using Handlebars to convert the Mustache <code class="literal">{{{ }}}</code> templates into
|
|
proper values. Additionally we’re registering 2 helper functions. <code class="literal">escapejsonbody</code> - that escapes the request
|
|
body in a format that can be embedded in a JSON. Another is <code class="literal">jsonpath</code> that for a given parameter knows how to
|
|
find an object in the request body.</p></td></tr></table></div></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_dynamic_properties_in_matchers_sections" href="#_dynamic_properties_in_matchers_sections"></a>7.5.6 Dynamic properties in matchers sections</h3></div></div></div><p>If you’ve been working with <a class="link" href="https://docs.pact.io/" target="_top">Pact</a> this might seem familiar. Quite a few users
|
|
are used to having a separation between the body and setting dynamic parts of your contract.</p><p>That’s why you can profit from two separate sections. One is called <code class="literal">stubMatchers</code> where you can
|
|
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. The other is called <code class="literal">testMatchers</code> which is present in the <code class="literal">response</code> or
|
|
<code class="literal">outputMessage</code> side of the contract.</p><p>Currently we support only JSON Path based matchers with the following matching possibilities.
|
|
For <code class="literal">stubMatchers</code>:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><code class="literal">byEquality()</code> - the value taken from the response via the provided JSON Path needs
|
|
to be equal to the provided value in the contract</li><li class="listitem"><code class="literal">byRegex(…​)</code> - the value taken from the response via the provided JSON Path needs
|
|
to match the regex</li><li class="listitem"><code class="literal">byDate()</code> - the value taken from the response via the provided JSON Path needs to
|
|
match the regex for ISO Date</li><li class="listitem"><code class="literal">byTimestamp()</code> - the value taken from the response via the provided JSON Path needs
|
|
to match the regex for ISO DateTime</li><li class="listitem"><code class="literal">byTime()</code> - the value taken from the response via the provided JSON Path needs to
|
|
match the regex for ISO Time</li></ul></div><p>For <code class="literal">testMatchers</code>:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><code class="literal">byEquality()</code> - the value taken from the response via the provided JSON Path needs
|
|
to be equal to the provided value in the contract</li><li class="listitem"><code class="literal">byRegex(…​)</code> - the value taken from the response via the provided JSON Path needs
|
|
to match the regex</li><li class="listitem"><code class="literal">byDate()</code> - the value taken from the response via the provided JSON Path needs to
|
|
match the regex for ISO Date</li><li class="listitem"><code class="literal">byTimestamp()</code> - the value taken from the response via the provided JSON Path needs
|
|
to match the regex for ISO DateTime</li><li class="listitem"><code class="literal">byTime()</code> - the value taken from the response via the provided JSON Path needs to
|
|
match the regex for ISO Time</li><li class="listitem"><code class="literal">byType()</code> - the value taken from the response via the provided JSON Path needs to
|
|
be of the same type as the type defined in the body of the response in the contract.
|
|
<code class="literal">byType</code> can take a closure where you can set <code class="literal">minOccurrence</code> and <code class="literal">maxOccurrence</code>.
|
|
That way you can assert on the size of the flattened collection. To check the size
|
|
of an unflattened collection, use a custom method via <code class="literal">byCommand(…​)</code> testMatcher.</li><li class="listitem"><p class="simpara"><code class="literal">byCommand(…​)</code> - the value taken from the response via the provided JSON Path will be
|
|
passed as an input to the custom method that you’re providing. E.g. <code class="literal">byCommand('foo($it)')</code>
|
|
will result in calling a <code class="literal">foo</code> method to which the value matching the JSON Path will get
|
|
passed.</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: circle; "><li class="listitem"><p class="simpara">The type of the object read from the JSON can be one of the followings 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 in a JSON</li><li class="listitem"><code class="literal">JSONArray</code> if you point to a <code class="literal">List</code> in a JSON</li><li class="listitem"><code class="literal">Map</code> if you point to a <code class="literal">Map</code> in a JSON</li><li class="listitem">proper <code class="literal">Number</code> if you point to <code class="literal">Integer</code>, <code class="literal">Double</code> etc. in a JSON</li><li class="listitem"><code class="literal">Boolean</code> if you point to a <code class="literal">Boolean</code> in a JSON</li></ul></div></li></ul></div></li></ul></div><p>Let’s take a look at the following example:</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>
|
|
]
|
|
])
|
|
stubMatchers {
|
|
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>))
|
|
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()))
|
|
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()))
|
|
jsonPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'$.aBoolean'</span>, byRegex(anyBoolean()))
|
|
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 <span class="hl-number">200</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>,
|
|
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>
|
|
]
|
|
])
|
|
testMatchers {
|
|
<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>))
|
|
<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()))
|
|
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()))
|
|
jsonPath(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'$.aBoolean'</span>, byRegex(anyBoolean()))
|
|
<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())
|
|
}
|
|
headers {
|
|
contentType(applicationJson())
|
|
}
|
|
}
|
|
}</pre><p>In this example we’re providing the dynamic portions of the contract in the matchers sections.
|
|
For the request part you can see that for all fields but <code class="literal">valueWithoutAMatcher</code> we’re setting
|
|
explicitly the values of regular expressions we’d like the stub to contain. For the <code class="literal">valueWithoutAMatcher</code>
|
|
the verification will take place in the same way as without the usage of matchers - the test
|
|
will perform an equality check in this case.</p><p>For the response side in the <code class="literal">testMatchers</code> section we’re defining all the dynamic parts
|
|
in a similar manner. The only difference is that we have the <code class="literal">byType</code> matchers too. In that
|
|
case we’re checking 4 fields in the way that we’re verifying whether the response from the test
|
|
has a value whose JSON path matching the given field is of the same type as the one defined in the response body and:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">for <code class="literal">$.valueWithTypeMatch</code> - we’re just checking the whether the type is the same</li><li class="listitem">for <code class="literal">$.valueWithMin</code> - we’re checking the type and assert if the size is greater or equal to the min occurrence</li><li class="listitem">for <code class="literal">$.valueWithMax</code> - we’re checking the type and assert if the size is smaller or equal to the max occurrence</li><li class="listitem">for <code class="literal">$.valueWithMinMax</code> - we’re checking the type and assert if the size is between the min and max occurrence</li></ul></div><p>The resulting test would look more or less like this (note that we’re separating the autogenerated
|
|
assertions and the one from matchers with an <code class="literal">and</code> section):</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\"}"</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+)?"</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>)).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>)).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>)).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>)).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>)).hasSizeLessThanOrEqualTo(<span class="hl-number">0</span>);
|
|
assertThatValueIsANumber(parsedJson.read(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"$.duck"</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 we are calling the <code class="literal">assertThatValueIsANumber</code>. This method needs
|
|
to be defined in the test base class or should 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 we took the method name and passed the proper JSON path as a parameter to it.</p></td></tr></table></div><p>and the WireMock stub like this:</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">"$[?(@.['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'].['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">"$.['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">"$.['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">"$[?(@.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+)?)/)]"</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-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>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>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>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>valueWithMin\\<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">":[1,2,3],\\"</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>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>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>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><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-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 / response that the <code class="literal">matcher</code> is addressing
|
|
via the JSON Path will get removed from assertion. In case of verifying a collection you have to create
|
|
matchers for <span class="strong"><strong>all</strong></span> elements of the collection.</p></td></tr></table></div><p>Let’s look at 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 <span class="hl-number">200</span>
|
|
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>
|
|
]
|
|
]
|
|
)
|
|
testMatchers {
|
|
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>This will lead in creating the following test (showing just 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. That’s because only the first element of the array got asserted.
|
|
In order to fix this it’s best to apply the assertion to the whole <code class="literal">$.events</code> collection and assert it
|
|
via 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>7.6 JAX-RS support</h2></div></div></div><p>We support JAX-RS 2 Client API. Base class needs to define <code class="literal">protected WebTarget webTarget</code> and server initialization, right now the only option how to test JAX-RS API is to start a web server.</p><p>Request with a body needs to have a content type set otherwise <code class="literal">application/octet-stream</code> is going to be 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>Example of a test API 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-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>7.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> etc. then inside your contract you have to provide in the <code class="literal">response</code>
|
|
section a <code class="literal">async()</code> method. Example:</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>
|
|
async()
|
|
}
|
|
}</pre></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>7.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 thing that changes in order to fully support context paths is the switch
|
|
on the <span class="strong"><strong>PRODUCER</strong></span> side. The autogenerated tests need to be using the <span class="strong"><strong>EXPLICIT</strong></span> mode.</p></td></tr></table></div><p>The consumer side remains untouched, in order for the generated test to pass you have to switch the <span class="strong"><strong>EXPLICIT</strong></span> 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>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’ll generate a test that <span class="strong"><strong>DOES NOT</strong></span> use MockMvc. It means that you’re generating
|
|
real requests and you need to setup your generated test’s base class to work on a real socket.</p><p>Let’s imagine 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 <span class="hl-number">200</span>
|
|
}
|
|
}</pre><p>Here is an example of how to set up a base class and Rest Assured for everything to work correctly.</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.context.embedded.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>That way all:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">all your requests in the autogenerated tests will be sent to the real endpoint with your context path included (e.g. <code class="literal">/my-context-path/url</code>)</li><li class="listitem">your contracts reflect that you have a context path, thus your generated stubs will also
|
|
have that information (e.g. in the stubs you’ll see that you have too 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="_messaging_top_level_elements" href="#_messaging_top_level_elements"></a>7.9 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.</p><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_output_triggered_by_a_method" href="#_output_triggered_by_a_method"></a>7.9.1 Output triggered by a method</h3></div></div></div><p>The output message can be triggered by calling a method (e.g. a Scheduler was started and a message was sent)</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>In this case the output message will be sent to <code class="literal">output</code> if a method called <code class="literal">bookReturnedTriggered</code> will be executed. In the message <span class="strong"><strong>publisher’s</strong></span> side
|
|
we will generate a test that will call 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="_output_triggered_by_a_message" href="#_output_triggered_by_a_message"></a>7.9.2 Output triggered by a message</h3></div></div></div><p>The output message can be triggered by receiving a message.</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>In this case the output message will be sent to <code class="literal">output</code> if a proper message will be received on the <code class="literal">input</code> destination. In the message <span class="strong"><strong>publisher’s</strong></span> side
|
|
we will generate a test that will send 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 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="_consumer_producer" href="#_consumer_producer"></a>7.9.3 Consumer / Producer</h3></div></div></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 use them also in messaging but we’re providing also the <code class="literal">consumer</code> and <code class="literal">produer</code> methods
|
|
as presented below (note 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><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>7.10 Multiple contracts in one file</h2></div></div></div><p>It’s possible to define multiple contracts in one file. An example of such a contract can look like this</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 <span class="hl-number">200</span>
|
|
}
|
|
},
|
|
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 <span class="hl-number">200</span>
|
|
}
|
|
}
|
|
]</pre><p>In this example one contract has the <code class="literal">name</code> field and the other doesn’t. This will lead to generation of
|
|
two tests that will 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 doesn’t have the name it’s 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 will look like this</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
|
|
contract had index <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’s much better if you name your contracts since then your tests
|
|
are far more meaningful.</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"> </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">6. Stub Runner for Messaging </td><td width="20%" align="center"><a accesskey="h" href="multi_spring-cloud-contract.html">Home</a></td><td width="40%" align="right" valign="top"> 8. Customization</td></tr></table></div></body></html> |