Sync docs from master to gh-pages
This commit is contained in:
@@ -27,7 +27,8 @@ contracts, one for the positive case and one for the negative case. Contract tes
|
||||
used to test contracts between applications and not to simulate full behavior.</p></td></tr></table></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_how_it_works" href="#_how_it_works"></a>2.3 How It Works</h2></div></div></div><p>This section explores how Spring Cloud Contract Verifier with Stub Runner works.</p><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_defining_the_contract" href="#_defining_the_contract"></a>2.3.1 Defining the contract</h3></div></div></div><p>As consumers of services, we need to define what exactly we want to achieve. We need to
|
||||
formulate our expectations. That is why we write contracts.</p><p>Assume that you want to send a request containing the ID of a client company and the
|
||||
amount it wants to borrow from us. You also want to send it to the /fraudcheck url via
|
||||
the PUT method.</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">package</span> contracts
|
||||
the PUT method.</p><p><b>Groovy DSL. </b>
|
||||
</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">package</span> contracts
|
||||
|
||||
org.springframework.cloud.contract.spec.Contract.make {
|
||||
request { <span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// (1)</span>
|
||||
@@ -83,7 +84,61 @@ From the Producer perspective, in the autogenerated producer-side test:
|
||||
(8) - and JSON body equal to
|
||||
{ "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" }
|
||||
(9) - with header `Content-Type` matching `application/json.*`
|
||||
*/</span></pre></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_client_side" href="#_client_side"></a>2.3.2 Client Side</h3></div></div></div><p>Spring Cloud Contract generates stubs, which you can use during client-side testing.
|
||||
*/</span></pre><p>
|
||||
</p><p><b>YAML. </b>
|
||||
</p><pre class="programlisting">request: # (1)
|
||||
method: PUT # (2)
|
||||
url: /fraudcheck # (3)
|
||||
body: # (4)
|
||||
"client.id": 1234567890
|
||||
loanAmount: 99999
|
||||
headers: # (5)
|
||||
Content-Type: application/json
|
||||
matchers:
|
||||
body:
|
||||
- path: $.['client.id'] # (6)
|
||||
type: by_regex
|
||||
value: "[0-9]{10}"
|
||||
response: # (7)
|
||||
status: 200 # (8)
|
||||
body: # (9)
|
||||
fraudCheckStatus: "FRAUD"
|
||||
"rejection.reason": "Amount too high"
|
||||
headers: # (10)
|
||||
Content-Type: application/json;charset=UTF-8
|
||||
|
||||
|
||||
#From the Consumer perspective, when shooting a request in the integration test:
|
||||
#
|
||||
#(1) - If the consumer sends a request
|
||||
#(2) - With the "PUT" method
|
||||
#(3) - to the URL "/fraudcheck"
|
||||
#(4) - with the JSON body that
|
||||
# * has a field `clientId`
|
||||
# * has a field `loanAmount` that is equal to `99999`
|
||||
#(5) - with header `Content-Type` equal to `application/json`
|
||||
#(6) - and a `client.id` json entry matches the regular expression `[0-9]{10}`
|
||||
#(7) - then the response will be sent with
|
||||
#(8) - status equal `200`
|
||||
#(9) - and JSON body equal to
|
||||
# { "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" }
|
||||
#(10) - with header `Content-Type` equal to `application/json`
|
||||
#
|
||||
#From the Producer perspective, in the autogenerated producer-side test:
|
||||
#
|
||||
#(1) - A request will be sent to the producer
|
||||
#(2) - With the "PUT" method
|
||||
#(3) - to the URL "/fraudcheck"
|
||||
#(4) - with the JSON body that
|
||||
# * has a field `clientId` `1234567890`
|
||||
# * has a field `loanAmount` that is equal to `99999`
|
||||
#(5) - with header `Content-Type` equal to `application/json`
|
||||
#(7) - then the test will assert if the response has been sent with
|
||||
#(8) - status equal `200`
|
||||
#(9) - and JSON body equal to
|
||||
# { "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" }
|
||||
#(10) - with header `Content-Type` equal to `application/json;charset=UTF-8`</pre><p>
|
||||
</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_client_side" href="#_client_side"></a>2.3.2 Client Side</h3></div></div></div><p>Spring Cloud Contract generates stubs, which you can use during client-side testing.
|
||||
You get a running WireMock instance/Messaging route that simulates the service.
|
||||
You would like to feed that instance with a proper stub definition.</p><p>At some point in time, you need to send a request to the Fraud Detection service.</p><pre class="programlisting">ResponseEntity<FraudServiceResponse> response =
|
||||
restTemplate.exchange(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"http://localhost:"</span> + port + <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"/fraudcheck"</span>, HttpMethod.PUT,
|
||||
@@ -98,7 +153,7 @@ You would like to feed that instance with a proper stub definition.</p><p>At som
|
||||
(or random) port.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_server_side" href="#_server_side"></a>2.3.3 Server Side</h3></div></div></div><p>Since you are developing your stub, you need to be sure that it actually resembles your
|
||||
concrete implementation. You cannot have a situation where your stub acts in one way and
|
||||
your application behaves in a different way, especially in production.</p><p>To ensure that your application behaves the way you define in your stub, tests are
|
||||
generated from the stub you provide.</p><p>The autogenerated test looks like this:</p><pre class="programlisting"><em><span class="hl-annotation" style="color: gray">@Test</span></em>
|
||||
generated from the stub you provide.</p><p>The autogenerated test looks, more or less, like this:</p><pre class="programlisting"><em><span class="hl-annotation" style="color: gray">@Test</span></em>
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">void</span> validate_shouldMarkClientAsFraud() <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()
|
||||
@@ -207,9 +262,10 @@ client wants to borrow. You want to send it to the <code class="literal">/fraudc
|
||||
FraudServiceResponse.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>);</pre><p>For simplicity, the port of the Fraud Detection service is set to <code class="literal">8080</code>, and the
|
||||
application runs on <code class="literal">8090</code>.</p><p>If you start the test at this point, it breaks, because no service currently runs on port
|
||||
<code class="literal">8080</code>.</p><p><span class="strong"><strong>Clone the Fraud Detection service repository locally.</strong></span></p><p>You can start by playing around with the server side contract. To do so, you must first
|
||||
clone it.</p><pre class="programlisting">git clone https://your-git-server.com/server-side.git local-http-server-repo</pre><p><span class="strong"><strong>Define the contract locally in the repo of Fraud Detection service.</strong></span></p><p>As a consumer, you need to define what exactly you want to achieve. You need to formulate
|
||||
clone it.</p><pre class="programlisting">$ git clone https://your-git-server.com/server-side.git local-http-server-repo</pre><p><span class="strong"><strong>Define the contract locally in the repo of Fraud Detection service.</strong></span></p><p>As a consumer, you need to define what exactly you want to achieve. You need to formulate
|
||||
your expectations. To do so, write the following contract:</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>Place the contract under <code class="literal">src/test/resources/contracts/fraud</code> folder. The <code class="literal">fraud</code> folder
|
||||
is important because the producer’s test base class name references that folder.</p></td></tr></table></div><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">package</span> contracts
|
||||
is important because the producer’s test base class name references that folder.</p></td></tr></table></div><p><b>Groovy DSL. </b>
|
||||
</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">package</span> contracts
|
||||
|
||||
org.springframework.cloud.contract.spec.Contract.make {
|
||||
request { <span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// (1)</span>
|
||||
@@ -265,8 +321,63 @@ From the Producer perspective, in the autogenerated producer-side test:
|
||||
(8) - and JSON body equal to
|
||||
{ "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" }
|
||||
(9) - with header `Content-Type` matching `application/json.*`
|
||||
*/</span></pre><p>The Contract is written using a statically typed Groovy DSL. You might wonder what about
|
||||
those <code class="literal">value(client(…​), server(…​))</code> parts. By using this notation, Spring Cloud
|
||||
*/</span></pre><p>
|
||||
</p><p><b>YAML. </b>
|
||||
</p><pre class="programlisting">request: # (1)
|
||||
method: PUT # (2)
|
||||
url: /fraudcheck # (3)
|
||||
body: # (4)
|
||||
"client.id": 1234567890
|
||||
loanAmount: 99999
|
||||
headers: # (5)
|
||||
Content-Type: application/json
|
||||
matchers:
|
||||
body:
|
||||
- path: $.['client.id'] # (6)
|
||||
type: by_regex
|
||||
value: "[0-9]{10}"
|
||||
response: # (7)
|
||||
status: 200 # (8)
|
||||
body: # (9)
|
||||
fraudCheckStatus: "FRAUD"
|
||||
"rejection.reason": "Amount too high"
|
||||
headers: # (10)
|
||||
Content-Type: application/json;charset=UTF-8
|
||||
|
||||
|
||||
#From the Consumer perspective, when shooting a request in the integration test:
|
||||
#
|
||||
#(1) - If the consumer sends a request
|
||||
#(2) - With the "PUT" method
|
||||
#(3) - to the URL "/fraudcheck"
|
||||
#(4) - with the JSON body that
|
||||
# * has a field `clientId`
|
||||
# * has a field `loanAmount` that is equal to `99999`
|
||||
#(5) - with header `Content-Type` equal to `application/json`
|
||||
#(6) - and a `client.id` json entry matches the regular expression `[0-9]{10}`
|
||||
#(7) - then the response will be sent with
|
||||
#(8) - status equal `200`
|
||||
#(9) - and JSON body equal to
|
||||
# { "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" }
|
||||
#(10) - with header `Content-Type` equal to `application/json`
|
||||
#
|
||||
#From the Producer perspective, in the autogenerated producer-side test:
|
||||
#
|
||||
#(1) - A request will be sent to the producer
|
||||
#(2) - With the "PUT" method
|
||||
#(3) - to the URL "/fraudcheck"
|
||||
#(4) - with the JSON body that
|
||||
# * has a field `clientId` `1234567890`
|
||||
# * has a field `loanAmount` that is equal to `99999`
|
||||
#(5) - with header `Content-Type` equal to `application/json`
|
||||
#(7) - then the test will assert if the response has been sent with
|
||||
#(8) - status equal `200`
|
||||
#(9) - and JSON body equal to
|
||||
# { "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" }
|
||||
#(10) - with header `Content-Type` equal to `application/json;charset=UTF-8`</pre><p>
|
||||
</p><p>The YML contract is quite straight-forward. However when you take a look at the Contract
|
||||
written using a statically typed Groovy DSL - you might wonder what the
|
||||
<code class="literal">value(client(…​), server(…​))</code> parts are. By using this notation, Spring Cloud
|
||||
Contract lets you define parts of a JSON block, a URL, etc., which are dynamic. In case
|
||||
of an identifier or a timestamp, you need not hardcode a value. You want to allow some
|
||||
different ranges of values. To enable ranges of values, you can set regular expressions
|
||||
@@ -298,8 +409,8 @@ First, add the <code class="literal">Spring Cloud Contract</code> BOM.</p><pre c
|
||||
<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>Since the plugin was added, you get the <code class="literal">Spring Cloud Contract Verifier</code> features which,
|
||||
from the provided contracts:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">generate and run tests</li><li class="listitem">produce and install stubs</li></ul></div><p>You do not want to generate tests since you, as the consumer, want only to play with the
|
||||
stubs. You need to skip the test generation and execution. When you execute:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">cd</span> local-http-server-repo
|
||||
./mvnw clean install -DskipTests</pre><p>In the logs, you see something like this:</p><pre class="programlisting">[INFO] --- spring-cloud-contract-maven-plugin:<span class="hl-number">1.0</span>.<span class="hl-number">0.</span>BUILD-SNAPSHOT:generateStubs (default-generateStubs) @ http-server ---
|
||||
stubs. You need to skip the test generation and execution. When you execute:</p><pre class="programlisting">$ <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">cd</span> local-http-server-repo
|
||||
$ ./mvnw clean install -DskipTests</pre><p>In the logs, you see something like this:</p><pre class="programlisting">[INFO] --- spring-cloud-contract-maven-plugin:<span class="hl-number">1.0</span>.<span class="hl-number">0.</span>BUILD-SNAPSHOT:generateStubs (default-generateStubs) @ http-server ---
|
||||
[INFO] Building jar: /some/path/http-server/target/http-server-<span class="hl-number">0.0</span>.<span class="hl-number">1</span>-SNAPSHOT-stubs.jar
|
||||
[INFO]
|
||||
[INFO] --- maven-jar-plugin:<span class="hl-number">2.6</span>:jar (default-jar) @ http-server ---
|
||||
@@ -349,8 +460,8 @@ you wish.</p><p>Once you are satisfied with the results and the test passes, pub
|
||||
the server side. Currently, the consumer side work is done.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_producer_side_fraud_detection_server" href="#_producer_side_fraud_detection_server"></a>2.4.3 Producer side (Fraud Detection server)</h3></div></div></div><p>As a developer of the Fraud Detection server (a server to the Loan Issuance service):</p><p><span class="strong"><strong>Create an initial implementation.</strong></span></p><p>As a reminder, you can see the initial implementation here:</p><pre class="programlisting"><em><span class="hl-annotation" style="color: gray">@RequestMapping(value = "/fraudcheck", method = PUT)</span></em>
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> FraudCheckResult fraudCheck(<em><span class="hl-annotation" style="color: gray">@RequestBody</span></em> FraudCheck fraudCheck) {
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> FraudCheckResult(FraudCheckStatus.OK, NO_REASON);
|
||||
}</pre><p><span class="strong"><strong>Take over the pull request.</strong></span></p><pre class="programlisting">git checkout -b contract-change-pr master
|
||||
git pull https://your-git-server.com/server-side-fork.git contract-change-pr</pre><p>You must add the dependencies needed by the autogenerated tests:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><dependency></span>
|
||||
}</pre><p><span class="strong"><strong>Take over the pull request.</strong></span></p><pre class="programlisting">$ git checkout -b contract-change-pr master
|
||||
$ git pull https://your-git-server.com/server-side-fork.git contract-change-pr</pre><p>You must add the dependencies needed by the autogenerated tests:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><dependency></span>
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><groupId></span>org.springframework.cloud<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></groupId></span>
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><artifactId></span>spring-cloud-starter-contract-verifier<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></artifactId></span>
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><scope></span>test<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></scope></span>
|
||||
@@ -420,8 +531,9 @@ like this:</p><pre class="programlisting"><em><span class="hl-annotation" style=
|
||||
DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());
|
||||
assertThatJson(parsedJson).field(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"['fraudCheckStatus']"</span>).matches(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"[A-Z]{5}"</span>);
|
||||
assertThatJson(parsedJson).field(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"['rejection.reason']"</span>).isEqualTo(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"Amount too high"</span>);
|
||||
}</pre><p>As you can see, all the <code class="literal">producer()</code> parts of the Contract that were present in the
|
||||
<code class="literal">value(consumer(…​), producer(…​))</code> blocks got injected into the test.</p><p>Note that, on the producer side, you are also doing TDD. The expectations are expressed
|
||||
}</pre><p>If you used the Groovy DSL, you can see, all the <code class="literal">producer()</code> parts of the Contract that were present in the
|
||||
<code class="literal">value(consumer(…​), producer(…​))</code> blocks got injected into the test.
|
||||
In case of using YAML, the same applied for the <code class="literal">matchers</code> sections of the <code class="literal">response</code>.</p><p>Note that, on the producer side, you are also doing TDD. The expectations are expressed
|
||||
in the form of a test. This test sends a request to our own application with the URL,
|
||||
headers, and body defined in the contract. It also is expecting precisely defined values
|
||||
in the response. In other words, you have the <code class="literal">red</code> part of <code class="literal">red</code>, <code class="literal">green</code>, and
|
||||
@@ -434,11 +546,11 @@ implementation:</p><pre class="programlisting"><em><span class="hl-annotation" s
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> FraudCheckResult(FraudCheckStatus.OK, NO_REASON);
|
||||
}</pre><p>When you execute <code class="literal">./mvnw clean install</code> again, the tests pass. Since the <code class="literal">Spring Cloud
|
||||
Contract Verifier</code> plugin adds the tests to the <code class="literal">generated-test-sources</code>, you can
|
||||
actually run those tests from your IDE.</p><p><span class="strong"><strong>Deploy your app.</strong></span></p><p>Once you finish your work, you can deploy your change. First, merge the branch:</p><pre class="programlisting">git checkout master
|
||||
git merge --no-ff contract-change-pr
|
||||
git push origin master</pre><p>Your CI might run something like <code class="literal">./mvnw clean deploy</code>, which would publish both the
|
||||
application and the stub artifacts.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_consumer_side_loan_issuance_final_step" href="#_consumer_side_loan_issuance_final_step"></a>2.4.4 Consumer Side (Loan Issuance) Final Step</h3></div></div></div><p>As a developer of the Loan Issuance service (a consumer of the Fraud Detection server):</p><p><span class="strong"><strong>Merge branch to master.</strong></span></p><pre class="programlisting">git checkout master
|
||||
git merge --no-ff contract-change-pr</pre><p><span class="strong"><strong>Work online.</strong></span></p><p>Now you can disable the offline work for Spring Cloud Contract Stub Runner and indicate
|
||||
actually run those tests from your IDE.</p><p><span class="strong"><strong>Deploy your app.</strong></span></p><p>Once you finish your work, you can deploy your change. First, merge the branch:</p><pre class="programlisting">$ git checkout master
|
||||
$ git merge --no-ff contract-change-pr
|
||||
$ git push origin master</pre><p>Your CI might run something like <code class="literal">./mvnw clean deploy</code>, which would publish both the
|
||||
application and the stub artifacts.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_consumer_side_loan_issuance_final_step" href="#_consumer_side_loan_issuance_final_step"></a>2.4.4 Consumer Side (Loan Issuance) Final Step</h3></div></div></div><p>As a developer of the Loan Issuance service (a consumer of the Fraud Detection server):</p><p><span class="strong"><strong>Merge branch to master.</strong></span></p><pre class="programlisting">$ git checkout master
|
||||
$ git merge --no-ff contract-change-pr</pre><p><span class="strong"><strong>Work online.</strong></span></p><p>Now you can disable the offline work for Spring Cloud Contract Stub Runner and indicate
|
||||
where the repository with your stubs is located. At this moment the stubs of the server
|
||||
side are automatically downloaded from Nexus/Artifactory. You can set the value of
|
||||
<code class="literal">stubsMode</code> to <code class="literal">REMOTE</code>. The following code shows an example of
|
||||
|
||||
Reference in New Issue
Block a user