794 lines
108 KiB
HTML
794 lines
108 KiB
HTML
<html><head>
|
|
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
|
<title>86. Spring Cloud Contract Verifier Introduction</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.html" title="Spring Cloud"><link rel="up" href="multi__spring_cloud_contract.html" title="Part XIII. Spring Cloud Contract"><link rel="prev" href="multi__spring_cloud_contract_2.html" title="85. Spring Cloud Contract"><link rel="next" href="multi__spring_cloud_contract_faq.html" title="87. Spring Cloud Contract FAQ"></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">86. Spring Cloud Contract Verifier Introduction</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="multi__spring_cloud_contract_2.html">Prev</a> </td><th width="60%" align="center">Part XIII. Spring Cloud Contract</th><td width="20%" align="right"> <a accesskey="n" href="multi__spring_cloud_contract_faq.html">Next</a></td></tr></table><hr></div><div class="chapter"><div class="titlepage"><div><div><h2 class="title"><a name="_spring_cloud_contract_verifier_introduction" href="#_spring_cloud_contract_verifier_introduction"></a>86. Spring Cloud Contract Verifier Introduction</h2></div></div></div><p>Spring Cloud Contract Verifier enables Consumer Driven Contract (CDC) development of
|
|
JVM-based applications. It moves TDD to the level of software architecture.</p><p>Spring Cloud Contract Verifier ships with <span class="emphasis"><em>Contract Definition Language</em></span> (CDL). Contract
|
|
definitions are used to produce the following resources:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">JSON stub definitions to be used by WireMock when doing integration testing on the
|
|
client code (<span class="emphasis"><em>client tests</em></span>). Test code must still be written by hand, and test data is
|
|
produced by Spring Cloud Contract Verifier.</li><li class="listitem">Messaging routes, if you’re using a messaging service. We integrate with Spring
|
|
Integration, Spring Cloud Stream, Spring AMQP, and Apache Camel. You can also set your
|
|
own integrations.</li><li class="listitem">Acceptance tests (in JUnit 4, JUnit 5 or Spock) are used to verify if server-side implementation
|
|
of the API is compliant with the contract (<span class="emphasis"><em>server tests</em></span>). A full test is generated by
|
|
Spring Cloud Contract Verifier.</li></ul></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_history" href="#_history"></a>86.1 History</h2></div></div></div><p>Before becoming Spring Cloud Contract, this project was called <a class="link" href="https://github.com/Codearte/accurest" target="_top">Accurest</a>.
|
|
It was created by <a class="link" href="https://twitter.com/mgrzejszczak" target="_top">Marcin Grzejszczak</a> and <a class="link" href="https://twitter.com/jkubrynski" target="_top">Jakub Kubrynski</a>
|
|
from (<a class="link" href="http://codearte.io" target="_top">codearte.io</a>.</p><p>The <code class="literal">0.1.0</code> release took place on 26 Jan 2015 and it became stable with <code class="literal">1.0.0</code> release on 29 Feb 2016.</p></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_why_a_contract_verifier" href="#_why_a_contract_verifier"></a>86.2 Why a Contract Verifier?</h2></div></div></div><p>Assume that we have a system consisting of multiple microservices:</p><div class="informalfigure"><div class="mediaobject"><img src="https://raw.githubusercontent.com/spring-cloud/spring-cloud-contract/master/docs/src/main/asciidoc/images/Deps.png" alt="Microservices Architecture"></div></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_testing_issues" href="#_testing_issues"></a>86.2.1 Testing issues</h3></div></div></div><p>If we wanted to test the application in top left corner to determine whether it can
|
|
communicate with other services, we could do one of two things:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">Deploy all microservices and perform end-to-end tests.</li><li class="listitem">Mock other microservices in unit/integration tests.</li></ul></div><p>Both have their advantages but also a lot of disadvantages.</p><p><span class="strong"><strong>Deploy all microservices and perform end to end tests</strong></span></p><p>Advantages:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">Simulates production.</li><li class="listitem">Tests real communication between services.</li></ul></div><p>Disadvantages:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">To test one microservice, we have to deploy 6 microservices, a couple of databases,
|
|
etc.</li><li class="listitem">The environment where the tests run is locked for a single suite of tests (nobody else
|
|
would be able to run the tests in the meantime).</li><li class="listitem">They take a long time to run.</li><li class="listitem">The feedback comes very late in the process.</li><li class="listitem">They are extremely hard to debug.</li></ul></div><p><span class="strong"><strong>Mock other microservices in unit/integration tests</strong></span></p><p>Advantages:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">They provide very fast feedback.</li><li class="listitem">They have no infrastructure requirements.</li></ul></div><p>Disadvantages:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">The implementor of the service creates stubs that might have nothing to do with
|
|
reality.</li><li class="listitem">You can go to production with passing tests and failing production.</li></ul></div><p>To solve the aforementioned issues, Spring Cloud Contract Verifier with Stub Runner was
|
|
created. The main idea is to give you very fast feedback, without the need to set up the
|
|
whole world of microservices. If you work on stubs, then the only applications you need
|
|
are those that your application directly uses.</p><div class="informalfigure"><div class="mediaobject"><img src="https://raw.githubusercontent.com/spring-cloud/spring-cloud-contract/master/docs/src/main/asciidoc/images/Stubs2.png" alt="Stubbed Services"></div></div><p>Spring Cloud Contract Verifier gives you the certainty that the stubs that you use were
|
|
created by the service that you’re calling. Also, if you can use them, it means that they
|
|
were tested against the producer’s side. In short, you can trust those stubs.</p></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_purposes" href="#_purposes"></a>86.3 Purposes</h2></div></div></div><p>The main purposes of Spring Cloud Contract Verifier with Stub Runner are:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">To ensure that WireMock/Messaging stubs (used when developing the client) do exactly
|
|
what the actual server-side implementation does.</li><li class="listitem">To promote ATDD method and Microservices architectural style.</li><li class="listitem">To provide a way to publish changes in contracts that are immediately visible on both
|
|
sides.</li><li class="listitem">To generate boilerplate test code to be used on the server side.</li></ul></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>Spring Cloud Contract Verifier’s purpose is NOT to start writing business
|
|
features in the contracts. Assume that we have a business use case of fraud check. If a
|
|
user can be a fraud for 100 different reasons, we would assume that you would create 2
|
|
contracts, one for the positive case and one for the negative case. Contract tests are
|
|
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>86.4 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="spring-cloud-contract-verifier-intro-three-second-tour" href="#spring-cloud-contract-verifier-intro-three-second-tour"></a>86.4.1 A Three-second Tour</h3></div></div></div><p>This very brief tour walks through using Spring Cloud Contract:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><a class="xref" href="multi__spring_cloud_contract_verifier_introduction.html#spring-cloud-contract-verifier-intro-three-second-tour-producer" title="On the Producer Side">the section called “On the Producer Side”</a></li><li class="listitem"><a class="xref" href="multi__spring_cloud_contract_verifier_introduction.html#spring-cloud-contract-verifier-intro-three-second-tour-consumer" title="On the Consumer Side">the section called “On the Consumer Side”</a></li></ul></div><p>You can find a somewhat longer tour
|
|
<a class="link" href="multi__spring_cloud_contract_verifier_introduction.html#spring-cloud-contract-verifier-intro-three-minute-tour" title="86.4.2 A Three-minute Tour">here</a>.</p><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="spring-cloud-contract-verifier-intro-three-second-tour-producer" href="#spring-cloud-contract-verifier-intro-three-second-tour-producer"></a>On the Producer Side</h4></div></div></div><p>To start working with Spring Cloud Contract, add files with <code class="literal">REST/</code> messaging contracts
|
|
expressed in either Groovy DSL or YAML to the contracts directory, which is set by the
|
|
<code class="literal">contractsDslDir</code> property. By default, it is <code class="literal">$rootDir/src/test/resources/contracts</code>.</p><p>Then add the Spring Cloud Contract Verifier dependency and plugin to your build file, as
|
|
shown in the following example:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><dependency></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><groupId></span>org.springframework.cloud<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></groupId></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><artifactId></span>spring-cloud-starter-contract-verifier<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></artifactId></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><scope></span>test<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></scope></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></dependency></span></pre><p>The following listing shows how to add the plugin, which should go in the build/plugins
|
|
portion of the file:</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"></plugin></span></pre><p>Running <code class="literal">./mvnw clean install</code> automatically generates tests that verify the application
|
|
compliance with the added contracts. By default, the tests get generated under
|
|
<code class="literal">org.springframework.cloud.contract.verifier.tests.</code>.</p><p>As the implementation of the functionalities described by the contracts is not yet
|
|
present, the tests fail.</p><p>To make them pass, you must add the correct implementation of either handling HTTP
|
|
requests or messages. Also, you must add a correct base test class for auto-generated
|
|
tests to the project. This class is extended by all the auto-generated tests, and it
|
|
should contain all the setup necessary to run them (for example <code class="literal">RestAssuredMockMvc</code>
|
|
controller setup or messaging test setup).</p><p>Once the implementation and the test base class are in place, the tests pass, and both the
|
|
application and the stub artifacts are built and installed in the local Maven repository.
|
|
The changes can now be merged, and both the application and the stub artifacts may be
|
|
published in an online repository.</p></div><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="spring-cloud-contract-verifier-intro-three-second-tour-consumer" href="#spring-cloud-contract-verifier-intro-three-second-tour-consumer"></a>On the Consumer Side</h4></div></div></div><p><code class="literal">Spring Cloud Contract Stub Runner</code> can be used in the integration tests to get a running
|
|
WireMock instance or messaging route that simulates the actual service.</p><p>To do so, add the dependency to <code class="literal">Spring Cloud Contract Stub Runner</code>, as shown in the
|
|
following example:</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-stub-runner<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></artifactId></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><scope></span>test<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></scope></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></dependency></span></pre><p>You can get the Producer-side stubs installed in your Maven repository in either of two
|
|
ways:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><p class="simpara">By checking out the Producer side repository and adding contracts and generating the stubs
|
|
by running the following commands:</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><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>The tests are being skipped because the Producer-side contract implementation is not
|
|
in place yet, so the automatically-generated contract tests fail.</p></td></tr></table></div></li><li class="listitem"><p class="simpara">By getting already-existing producer service stubs from a remote repository. To do so,
|
|
pass the stub artifact IDs and artifact repository URL as <code class="literal">Spring Cloud Contract
|
|
Stub Runner</code> properties, as shown in the following example:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute">stubrunner</span>:
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute"> ids</span>: <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'com.example:http-server-dsl:+:stubs:8080'</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute"> repositoryRoot</span>: http://repo.spring.io/libs-snapshot</pre></li></ul></div><p>Now you can annotate your test class with <code class="literal">@AutoConfigureStubRunner</code>. In the annotation,
|
|
provide the <code class="literal">group-id</code> and <code class="literal">artifact-id</code> values for <code class="literal">Spring Cloud Contract Stub Runner</code> to
|
|
run the collaborators' stubs for you, as shown in the following example:</p><pre class="programlisting"><em><span class="hl-annotation" style="color: gray">@RunWith(SpringRunner.class)</span></em>
|
|
<em><span class="hl-annotation" style="color: gray">@SpringBootTest(webEnvironment=WebEnvironment.NONE)</span></em>
|
|
<em><span class="hl-annotation" style="color: gray">@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:6565"},
|
|
stubsMode = StubRunnerProperties.StubsMode.LOCAL)</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">class</span> LoanApplicationServiceTests {</pre><div class="tip" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Tip"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Tip]" src="images/tip.png"></td><th align="left">Tip</th></tr><tr><td align="left" valign="top"><p>Use the <code class="literal">REMOTE</code> <code class="literal">stubsMode</code> when downloading stubs from an online repository and
|
|
<code class="literal">LOCAL</code> for offline work.</p></td></tr></table></div><p>Now, in your integration test, you can receive stubbed versions of HTTP responses or
|
|
messages that are expected to be emitted by the collaborator service.</p></div></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="spring-cloud-contract-verifier-intro-three-minute-tour" href="#spring-cloud-contract-verifier-intro-three-minute-tour"></a>86.4.2 A Three-minute Tour</h3></div></div></div><p>This brief tour walks through using Spring Cloud Contract:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><a class="xref" href="multi__spring_cloud_contract_verifier_introduction.html#spring-cloud-contract-verifier-intro-three-minute-tour-producer" title="On the Producer Side">the section called “On the Producer Side”</a></li><li class="listitem"><a class="xref" href="multi__spring_cloud_contract_verifier_introduction.html#spring-cloud-contract-verifier-intro-three-minute-tour-consumer" title="On the Consumer Side">the section called “On the Consumer Side”</a></li></ul></div><p>You can find an even more brief tour
|
|
<a class="link" href="multi__spring_cloud_contract_verifier_introduction.html#spring-cloud-contract-verifier-intro-three-second-tour" title="86.4.1 A Three-second Tour">here</a>.</p><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="spring-cloud-contract-verifier-intro-three-minute-tour-producer" href="#spring-cloud-contract-verifier-intro-three-minute-tour-producer"></a>On the Producer Side</h4></div></div></div><p>To start working with <code class="literal">Spring Cloud Contract</code>, add files with <code class="literal">REST/</code> messaging contracts
|
|
expressed in either Groovy DSL or YAML to the contracts directory, which is set by the
|
|
<code class="literal">contractsDslDir</code> property. By default, it is <code class="literal">$rootDir/src/test/resources/contracts</code>.</p><p>For the HTTP stubs, a contract defines what kind of response should be returned for a
|
|
given request (taking into account the HTTP methods, URLs, headers, status codes, and so
|
|
on). The following example shows how an HTTP stub contract in Groovy DSL:</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 {
|
|
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">'/fraudcheck'</span>
|
|
body([
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"client.id"</span>: $(regex(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'[0-9]{10}'</span>)),
|
|
loanAmount: <span class="hl-number">99999</span>
|
|
])
|
|
headers {
|
|
contentType(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'application/json'</span>)
|
|
}
|
|
}
|
|
response {
|
|
status OK()
|
|
body([
|
|
fraudCheckStatus: <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"FRAUD"</span>,
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"rejection.reason"</span>: <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"Amount too high"</span>
|
|
])
|
|
headers {
|
|
contentType(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'application/json'</span>)
|
|
}
|
|
}
|
|
}</pre><p>The same contract expressed in YAML would look like the following example:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute">request</span>:
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute"> method</span>: PUT
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute"> url</span>: /fraudcheck
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute"> body</span>:
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute"> "client.id"</span>: <span class="hl-number">1234567890</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute"> loanAmount</span>: <span class="hl-number">99999</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute"> headers</span>:
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute"> Content-Type</span>: application/json
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute"> matchers</span>:
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute"> body</span>:
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute"> - path</span>: $.[<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'client.id'</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">]</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute"> type</span>: by_regex
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute"> value</span>: <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"[0-9]{10}"</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute">response</span>:
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute"> status</span>: <span class="hl-number">200</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute"> body</span>:
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute"> fraudCheckStatus</span>: <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"FRAUD"</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute"> "rejection.reason"</span>: <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"Amount too high"</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute"> headers</span>:
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute"> Content-Type</span>: application/json;charset=UTF-<span class="hl-number">8</span></pre><p>In the case of messaging, you can define:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">The input and the output messages can be defined (taking into account from and where it
|
|
was sent, the message body, and the header).</li><li class="listitem">The methods that should be called after the message is received.</li><li class="listitem">The methods that, when called, should trigger a message.</li></ul></div><p>The following example shows a Camel messaging contract expressed in Groovy DSL:</p><pre class="programlisting">def contractDsl = Contract.make {
|
|
label <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'some_label'</span>
|
|
input {
|
|
messageFrom(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'jms:delete'</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>)
|
|
}
|
|
assertThat(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'bookWasDeleted()'</span>)
|
|
}
|
|
}</pre><p>The following example shows the same contract expressed in YAML:</p><pre class="programlisting">label: some_label
|
|
input:
|
|
messageFrom: jms:delete
|
|
messageBody:
|
|
bookName: 'foo'
|
|
messageHeaders:
|
|
sample: header
|
|
assertThat: bookWasDeleted()</pre><p>Then you can add Spring Cloud Contract Verifier dependency and plugin to your build file,
|
|
as shown in the following example:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><dependency></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><groupId></span>org.springframework.cloud<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></groupId></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><artifactId></span>spring-cloud-starter-contract-verifier<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></artifactId></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><scope></span>test<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></scope></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></dependency></span></pre><p>The following listing shows how to add the plugin, which should go in the build/plugins
|
|
portion of the file:</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"></plugin></span></pre><p>Running <code class="literal">./mvnw clean install</code> automatically generates tests that verify the application
|
|
compliance with the added contracts. By default, the generated tests are under
|
|
<code class="literal">org.springframework.cloud.contract.verifier.tests.</code>.</p><p>The following example shows a sample auto-generated test for an HTTP contract:</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()
|
|
.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.fraud.v1+json"</span>)
|
|
.body(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"{\"client.id\":\"1234567890\",\"loanAmount\":99999}"</span>);
|
|
|
|
<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">"/fraudcheck"</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/vnd.fraud.v1.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">"['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>The preceding example uses Spring’s <code class="literal">MockMvc</code> to run the tests. This is the default test
|
|
mode for HTTP contracts. However, JAX-RS client and explicit HTTP invocations can also be
|
|
used. (To do so, change the <code class="literal">testMode</code> property of the plugin to <code class="literal">JAX-RS</code> or <code class="literal">EXPLICIT</code>,
|
|
respectively.)</p><p>Since 2.1.0, it is also possible to use <code class="literal">RestAssuredWebTestClient`with Spring’s reactive `WebTestClient</code>
|
|
run under the hood. This is particularly recommended while working with Reactive, <code class="literal">Web-Flux</code>-based applications.
|
|
In order to use <code class="literal">WebTestClient</code> set <code class="literal">testMode</code> to <code class="literal">WEBTESTCLIENT</code>.</p><p>Here is an example of a test generated in <code class="literal">WEBTESTCLIENT</code> test mode:</p><pre class="literallayout">[source,java,indent=0]</pre><pre class="screen">@Test
|
|
public void validate_shouldRejectABeerIfTooYoung() throws Exception {
|
|
// given:
|
|
WebTestClientRequestSpecification request = given()
|
|
.header("Content-Type", "application/json")
|
|
.body("{\"age\":10}");
|
|
|
|
// when:
|
|
WebTestClientResponse response = given().spec(request)
|
|
.post("/check");
|
|
|
|
// then:
|
|
assertThat(response.statusCode()).isEqualTo(200);
|
|
assertThat(response.header("Content-Type")).matches("application/json.*");
|
|
// and:
|
|
DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());
|
|
assertThatJson(parsedJson).field("['status']").isEqualTo("NOT_OK");
|
|
}</pre><p>Apart from the default JUnit 4, you can instead use JUnit 5 or Spock tests, by setting the plugin
|
|
<code class="literal">testFramework</code> property to either <code class="literal">JUNIT5</code> or <code class="literal">Spock</code>.</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>You can now also generate WireMock scenarios based on the contracts, by including an
|
|
order number followed by an underscore at the beginning of the contract file names.</p></td></tr></table></div><p>The following example shows an auto-generated test in Spock for a messaging stub contract:</p><pre class="literallayout">[source,groovy,indent=0]</pre><pre class="screen">given:
|
|
ContractVerifierMessage inputMessage = contractVerifierMessaging.create(
|
|
\'\'\'{"bookName":"foo"}\'\'\',
|
|
['sample': 'header']
|
|
)
|
|
|
|
when:
|
|
contractVerifierMessaging.send(inputMessage, 'jms:delete')
|
|
|
|
then:
|
|
noExceptionThrown()
|
|
bookWasDeleted()</pre><p>As the implementation of the functionalities described by the contracts is not yet
|
|
present, the tests fail.</p><p>To make them pass, you must add the correct implementation of handling either HTTP
|
|
requests or messages. Also, you must add a correct base test class for auto-generated
|
|
tests to the project. This class is extended by all the auto-generated tests and should
|
|
contain all the setup necessary to run them (for example, <code class="literal">RestAssuredMockMvc</code> controller
|
|
setup or messaging test setup).</p><p>Once the implementation and the test base class are in place, the tests pass, and both the
|
|
application and the stub artifacts are built and installed in the local Maven repository.
|
|
Information about installing the stubs jar to the local repository appears in the logs, as
|
|
shown in the following example:</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 ---
|
|
[INFO] Building jar: /some/path/http-server/target/http-server-<span class="hl-number">0.0</span>.<span class="hl-number">1</span>-SNAPSHOT.jar
|
|
[INFO]
|
|
[INFO] --- spring-boot-maven-plugin:<span class="hl-number">1.5</span>.<span class="hl-number">5.</span>BUILD-SNAPSHOT:repackage (default) @ http-server ---
|
|
[INFO]
|
|
[INFO] --- maven-install-plugin:<span class="hl-number">2.5</span>.<span class="hl-number">2</span>:install (default-install) @ http-server ---
|
|
[INFO] Installing /some/path/http-server/target/http-server-<span class="hl-number">0.0</span>.<span class="hl-number">1</span>-SNAPSHOT.jar to /path/to/your/.m2/repository/com/example/http-server/<span class="hl-number">0.0</span>.<span class="hl-number">1</span>-SNAPSHOT/http-server-<span class="hl-number">0.0</span>.<span class="hl-number">1</span>-SNAPSHOT.jar
|
|
[INFO] Installing /some/path/http-server/pom.xml to /path/to/your/.m2/repository/com/example/http-server/<span class="hl-number">0.0</span>.<span class="hl-number">1</span>-SNAPSHOT/http-server-<span class="hl-number">0.0</span>.<span class="hl-number">1</span>-SNAPSHOT.pom
|
|
[INFO] Installing /some/path/http-server/target/http-server-<span class="hl-number">0.0</span>.<span class="hl-number">1</span>-SNAPSHOT-stubs.jar to /path/to/your/.m2/repository/com/example/http-server/<span class="hl-number">0.0</span>.<span class="hl-number">1</span>-SNAPSHOT/http-server-<span class="hl-number">0.0</span>.<span class="hl-number">1</span>-SNAPSHOT-stubs.jar</pre><p>You can now merge the changes and publish both the application and the stub artifacts
|
|
in an online repository.</p><p><span class="strong"><strong>Docker Project</strong></span></p><p>In order to enable working with contracts while creating applications in non-JVM
|
|
technologies, the <code class="literal">springcloud/spring-cloud-contract</code> Docker image has been created. It
|
|
contains a project that automatically generates tests for HTTP contracts and executes them
|
|
in <code class="literal">EXPLICIT</code> test mode. Then, if the tests pass, it generates Wiremock stubs and,
|
|
optionally, publishes them to an artifact manager. In order to use the image, you can
|
|
mount the contracts into the <code class="literal">/contracts</code> directory and set a few environment variables.</p></div><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="spring-cloud-contract-verifier-intro-three-minute-tour-consumer" href="#spring-cloud-contract-verifier-intro-three-minute-tour-consumer"></a>On the Consumer Side</h4></div></div></div><p><code class="literal">Spring Cloud Contract Stub Runner</code> can be used in the integration tests to get a running
|
|
WireMock instance or messaging route that simulates the actual service.</p><p>To get started, add the dependency to <code class="literal">Spring Cloud Contract Stub Runner</code>:</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-stub-runner<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></artifactId></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><scope></span>test<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></scope></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></dependency></span></pre><p>You can get the Producer-side stubs installed in your Maven repository in either of two
|
|
ways:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><p class="simpara">By checking out the Producer side repository and adding contracts and generating the
|
|
stubs by running the following commands:</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><div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Note]" src="images/note.png"></td><th align="left">Note</th></tr><tr><td align="left" valign="top"><p>The tests are skipped because the Producer-side contract implementation is not yet
|
|
in place, so the automatically-generated contract tests fail.</p></td></tr></table></div></li><li class="listitem"><p class="simpara">Getting already existing producer service stubs from a remote repository. To do so,
|
|
pass the stub artifact IDs and artifact repository URl as <code class="literal">Spring Cloud Contract Stub
|
|
Runner</code> properties, as shown in the following example:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute">stubrunner</span>:
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute"> ids</span>: <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'com.example:http-server-dsl:+:stubs:8080'</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute"> repositoryRoot</span>: http://repo.spring.io/libs-snapshot</pre></li></ul></div><p>Now you can annotate your test class with <code class="literal">@AutoConfigureStubRunner</code>. In the annotation,
|
|
provide the <code class="literal">group-id</code> and <code class="literal">artifact-id</code> for <code class="literal">Spring Cloud Contract Stub Runner</code> to run
|
|
the collaborators' stubs for you, as shown in the following example:</p><pre class="programlisting"><em><span class="hl-annotation" style="color: gray">@RunWith(SpringRunner.class)</span></em>
|
|
<em><span class="hl-annotation" style="color: gray">@SpringBootTest(webEnvironment=WebEnvironment.NONE)</span></em>
|
|
<em><span class="hl-annotation" style="color: gray">@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:6565"},
|
|
stubsMode = StubRunnerProperties.StubsMode.LOCAL)</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">class</span> LoanApplicationServiceTests {</pre><div class="tip" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Tip"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Tip]" src="images/tip.png"></td><th align="left">Tip</th></tr><tr><td align="left" valign="top"><p>Use the <code class="literal">REMOTE</code> <code class="literal">stubsMode</code> when downloading stubs from an online repository and
|
|
<code class="literal">LOCAL</code> for offline work.</p></td></tr></table></div><p>In your integration test, you can receive stubbed versions of HTTP responses or messages
|
|
that are expected to be emitted by the collaborator service. You can see entries similar
|
|
to the following in the build logs:</p><pre class="programlisting"><span class="hl-number">2016</span>-<span class="hl-number">07</span>-<span class="hl-number">19</span> <span class="hl-number">14</span>:<span class="hl-number">22</span>:<span class="hl-number">25.403</span> INFO <span class="hl-number">41050</span> --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Desired version is + - will try to resolve the latest version
|
|
<span class="hl-number">2016</span>-<span class="hl-number">07</span>-<span class="hl-number">19</span> <span class="hl-number">14</span>:<span class="hl-number">22</span>:<span class="hl-number">25.438</span> INFO <span class="hl-number">41050</span> --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Resolved version is <span class="hl-number">0.0</span>.<span class="hl-number">1</span>-SNAPSHOT
|
|
<span class="hl-number">2016</span>-<span class="hl-number">07</span>-<span class="hl-number">19</span> <span class="hl-number">14</span>:<span class="hl-number">22</span>:<span class="hl-number">25.439</span> INFO <span class="hl-number">41050</span> --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Resolving artifact com.example:http-server:jar:stubs:<span class="hl-number">0.0</span>.<span class="hl-number">1</span>-SNAPSHOT using remote repositories []
|
|
<span class="hl-number">2016</span>-<span class="hl-number">07</span>-<span class="hl-number">19</span> <span class="hl-number">14</span>:<span class="hl-number">22</span>:<span class="hl-number">25.451</span> INFO <span class="hl-number">41050</span> --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Resolved artifact com.example:http-server:jar:stubs:<span class="hl-number">0.0</span>.<span class="hl-number">1</span>-SNAPSHOT to /path/to/your/.m2/repository/com/example/http-server/<span class="hl-number">0.0</span>.<span class="hl-number">1</span>-SNAPSHOT/http-server-<span class="hl-number">0.0</span>.<span class="hl-number">1</span>-SNAPSHOT-stubs.jar
|
|
<span class="hl-number">2016</span>-<span class="hl-number">07</span>-<span class="hl-number">19</span> <span class="hl-number">14</span>:<span class="hl-number">22</span>:<span class="hl-number">25.465</span> INFO <span class="hl-number">41050</span> --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Unpacking stub from JAR [URI: file:/path/to/your/.m2/repository/com/example/http-server/<span class="hl-number">0.0</span>.<span class="hl-number">1</span>-SNAPSHOT/http-server-<span class="hl-number">0.0</span>.<span class="hl-number">1</span>-SNAPSHOT-stubs.jar]
|
|
<span class="hl-number">2016</span>-<span class="hl-number">07</span>-<span class="hl-number">19</span> <span class="hl-number">14</span>:<span class="hl-number">22</span>:<span class="hl-number">25.475</span> INFO <span class="hl-number">41050</span> --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Unpacked file to [/var/folders/<span class="hl-number">0</span>p/xwq47sq106x1_g3dtv6qfm940000gq/T/contracts100276532569594265]
|
|
<span class="hl-number">2016</span>-<span class="hl-number">07</span>-<span class="hl-number">19</span> <span class="hl-number">14</span>:<span class="hl-number">22</span>:<span class="hl-number">27.737</span> INFO <span class="hl-number">41050</span> --- [ main] o.s.c.c.stubrunner.StubRunnerExecutor : All stubs are now running RunningStubs [namesAndPorts={com.example:http-server:<span class="hl-number">0.0</span>.<span class="hl-number">1</span>-SNAPSHOT:stubs=<span class="hl-number">8080</span>}]</pre></div></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_defining_the_contract" href="#_defining_the_contract"></a>86.4.3 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><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>
|
|
method <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'PUT'</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// (2)</span>
|
|
url <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'/fraudcheck'</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// (3)</span>
|
|
body([ <span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// (4)</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"client.id"</span>: $(regex(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'[0-9]{10}'</span>)),
|
|
loanAmount: <span class="hl-number">99999</span>
|
|
])
|
|
headers { <span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// (5)</span>
|
|
contentType(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'application/json'</span>)
|
|
}
|
|
}
|
|
response { <span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// (6)</span>
|
|
status OK() <span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// (7)</span>
|
|
body([ <span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// (8)</span>
|
|
fraudCheckStatus: <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"FRAUD"</span>,
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"rejection.reason"</span>: <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"Amount too high"</span>
|
|
])
|
|
headers { <span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// (9)</span>
|
|
contentType(<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">/*
|
|
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 `client.id` that matches a regular expression `[0-9]{10}`
|
|
* has a field `loanAmount` that is equal to `99999`
|
|
(5) - with header `Content-Type` equal to `application/json`
|
|
(6) - then the response will be sent with
|
|
(7) - status equal `200`
|
|
(8) - and JSON body equal to
|
|
{ "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" }
|
|
(9) - 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 `client.id` that will have a generated value that matches a regular expression `[0-9]{10}`
|
|
* has a field `loanAmount` that is equal to `99999`
|
|
(5) - with header `Content-Type` equal to `application/json`
|
|
(6) - then the test will assert if the response has been sent with
|
|
(7) - status equal `200`
|
|
(8) - and JSON body equal to
|
|
{ "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" }
|
|
(9) - with header `Content-Type` matching `application/json.*`
|
|
*/</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 `client.id`
|
|
# * 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 `client.id` `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>86.4.4 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,
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> HttpEntity<>(request, httpHeaders),
|
|
FraudServiceResponse.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>);</pre><p>Annotate your test class with <code class="literal">@AutoConfigureStubRunner</code>. In the annotation provide the group id and artifact id for the Stub Runner to download stubs of your collaborators.</p><pre class="programlisting"><em><span class="hl-annotation" style="color: gray">@RunWith(SpringRunner.class)</span></em>
|
|
<em><span class="hl-annotation" style="color: gray">@SpringBootTest(webEnvironment=WebEnvironment.NONE)</span></em>
|
|
<em><span class="hl-annotation" style="color: gray">@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:6565"},
|
|
stubsMode = StubRunnerProperties.StubsMode.LOCAL)</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">class</span> LoanApplicationServiceTests {</pre><p>After that, during the tests, Spring Cloud Contract automatically finds the stubs
|
|
(simulating the real service) in the Maven repository and exposes them on a configured
|
|
(or random) port.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_server_side" href="#_server_side"></a>86.4.5 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, 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()
|
|
.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.fraud.v1+json"</span>)
|
|
.body(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"{\"client.id\":\"1234567890\",\"loanAmount\":99999}"</span>);
|
|
|
|
<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">"/fraudcheck"</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/vnd.fraud.v1.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">"['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></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_step_by_step_guide_to_consumer_driven_contracts_cdc" href="#_step_by_step_guide_to_consumer_driven_contracts_cdc"></a>86.5 Step-by-step Guide to Consumer Driven Contracts (CDC)</h2></div></div></div><p>Consider an example of Fraud Detection and the Loan Issuance process. The business
|
|
scenario is such that we want to issue loans to people but do not want them to steal from
|
|
us. The current implementation of our system grants loans to everybody.</p><p>Assume that <code class="literal">Loan Issuance</code> is a client to the <code class="literal">Fraud Detection</code> server. In the current
|
|
sprint, we must develop a new feature: if a client wants to borrow too much money, then
|
|
we mark the client as a fraud.</p><p>Technical remark - Fraud Detection has an <code class="literal">artifact-id</code> of <code class="literal">http-server</code>, while Loan
|
|
Issuance has an artifact-id of <code class="literal">http-client</code>, and both have a <code class="literal">group-id</code> of <code class="literal">com.example</code>.</p><p>Social remark - both client and server development teams need to communicate directly and
|
|
discuss changes while going through the process. CDC is all about communication.</p><p>The <a class="link" href="https://github.com/spring-cloud/spring-cloud-contract/tree/master/samples/standalone/dsl/http-server" target="_top">server
|
|
side code is available here</a> and <a class="link" href="https://github.com/spring-cloud/spring-cloud-contract/tree/master/samples/standalone/dsl/http-client" target="_top">the
|
|
client code here</a>.</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>In this case, the producer owns the contracts. Physically, all the contract are
|
|
in the producer’s repository.</p></td></tr></table></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_technical_note" href="#_technical_note"></a>86.5.1 Technical note</h3></div></div></div><p>If using the <span class="strong"><strong>SNAPSHOT</strong></span> / <span class="strong"><strong>Milestone</strong></span> / <span class="strong"><strong>Release Candidate</strong></span> versions please add the
|
|
following section to your build:</p><p class="primary"><b>Maven. </b>
|
|
</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><repositories></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><repository></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><id></span>spring-snapshots<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></id></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><name></span>Spring Snapshots<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></name></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><url></span>https://repo.spring.io/snapshot<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></url></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><snapshots></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><enabled></span>true<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></enabled></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></snapshots></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></repository></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><repository></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><id></span>spring-milestones<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></id></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><name></span>Spring Milestones<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></name></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><url></span>https://repo.spring.io/milestone<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></url></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><snapshots></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><enabled></span>false<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></enabled></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></snapshots></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></repository></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><repository></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><id></span>spring-releases<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></id></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><name></span>Spring Releases<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></name></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><url></span>https://repo.spring.io/release<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></url></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><snapshots></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><enabled></span>false<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></enabled></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></snapshots></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></repository></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></repositories></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><pluginRepositories></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><pluginRepository></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><id></span>spring-snapshots<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></id></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><name></span>Spring Snapshots<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></name></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><url></span>https://repo.spring.io/snapshot<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></url></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><snapshots></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><enabled></span>true<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></enabled></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></snapshots></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></pluginRepository></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><pluginRepository></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><id></span>spring-milestones<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></id></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><name></span>Spring Milestones<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></name></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><url></span>https://repo.spring.io/milestone<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></url></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><snapshots></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><enabled></span>false<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></enabled></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></snapshots></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></pluginRepository></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><pluginRepository></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><id></span>spring-releases<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></id></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><name></span>Spring Releases<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></name></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><url></span>https://repo.spring.io/release<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></url></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><snapshots></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><enabled></span>false<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></enabled></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></snapshots></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></pluginRepository></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></pluginRepositories></span></pre><p class="primary">
|
|
</p><p class="secondary"><b>Gradle. </b>
|
|
</p><pre class="programlisting">repositories {
|
|
mavenCentral()
|
|
mavenLocal()
|
|
maven { url <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"http://repo.spring.io/snapshot"</span> }
|
|
maven { url <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"http://repo.spring.io/milestone"</span> }
|
|
maven { url <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"http://repo.spring.io/release"</span> }
|
|
}</pre><p class="secondary">
|
|
</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_consumer_side_loan_issuance" href="#_consumer_side_loan_issuance"></a>86.5.2 Consumer side (Loan Issuance)</h3></div></div></div><p>As a developer of the Loan Issuance service (a consumer of the Fraud Detection server), you might do the following steps:</p><div class="orderedlist"><ol class="orderedlist" type="1"><li class="listitem">Start doing TDD by writing a test for your feature.</li><li class="listitem">Write the missing implementation.</li><li class="listitem">Clone the Fraud Detection service repository locally.</li><li class="listitem">Define the contract locally in the repo of Fraud Detection service.</li><li class="listitem">Add the Spring Cloud Contract Verifier plugin.</li><li class="listitem">Run the integration tests.</li><li class="listitem">File a pull request.</li><li class="listitem">Create an initial implementation.</li><li class="listitem">Take over the pull request.</li><li class="listitem">Write the missing implementation.</li><li class="listitem">Deploy your app.</li><li class="listitem">Work online.</li></ol></div><p><span class="strong"><strong>Start doing TDD by writing a test for your feature.</strong></span></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> shouldBeRejectedDueToAbnormalLoanAmount() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// given:</span>
|
|
LoanApplication application = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> LoanApplication(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> Client(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"1234567890"</span>),
|
|
<span class="hl-number">99999</span>);
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// when:</span>
|
|
LoanApplicationResult loanApplication = service.loanApplication(application);
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// then:</span>
|
|
assertThat(loanApplication.getLoanApplicationStatus())
|
|
.isEqualTo(LoanApplicationStatus.LOAN_APPLICATION_REJECTED);
|
|
assertThat(loanApplication.getRejectionReason()).isEqualTo(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"Amount too high"</span>);
|
|
}</pre><p>Assume that you have written a test of your new feature. If a loan application for a big
|
|
amount is received, the system should reject that loan application with some description.</p><p><span class="strong"><strong>Write the missing implementation.</strong></span></p><p>At some point in time, you need to send a request to the Fraud Detection service. Assume
|
|
that you need to send the request containing the ID of the client and the amount the
|
|
client wants to borrow. You want to send it to the <code class="literal">/fraudcheck</code> url via the <code class="literal">PUT</code> method.</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,
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> HttpEntity<>(request, httpHeaders),
|
|
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
|
|
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><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>
|
|
method <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'PUT'</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// (2)</span>
|
|
url <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'/fraudcheck'</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// (3)</span>
|
|
body([ <span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// (4)</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"client.id"</span>: $(regex(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'[0-9]{10}'</span>)),
|
|
loanAmount: <span class="hl-number">99999</span>
|
|
])
|
|
headers { <span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// (5)</span>
|
|
contentType(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'application/json'</span>)
|
|
}
|
|
}
|
|
response { <span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// (6)</span>
|
|
status OK() <span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// (7)</span>
|
|
body([ <span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// (8)</span>
|
|
fraudCheckStatus: <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"FRAUD"</span>,
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"rejection.reason"</span>: <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"Amount too high"</span>
|
|
])
|
|
headers { <span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// (9)</span>
|
|
contentType(<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">/*
|
|
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 `client.id` that matches a regular expression `[0-9]{10}`
|
|
* has a field `loanAmount` that is equal to `99999`
|
|
(5) - with header `Content-Type` equal to `application/json`
|
|
(6) - then the response will be sent with
|
|
(7) - status equal `200`
|
|
(8) - and JSON body equal to
|
|
{ "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" }
|
|
(9) - 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 `client.id` that will have a generated value that matches a regular expression `[0-9]{10}`
|
|
* has a field `loanAmount` that is equal to `99999`
|
|
(5) - with header `Content-Type` equal to `application/json`
|
|
(6) - then the test will assert if the response has been sent with
|
|
(7) - status equal `200`
|
|
(8) - and JSON body equal to
|
|
{ "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" }
|
|
(9) - with header `Content-Type` matching `application/json.*`
|
|
*/</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 `client.id`
|
|
# * 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 `client.id` `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
|
|
matching those values for the consumer side. You can provide the body by means of either
|
|
a map notation or String with interpolations.
|
|
Consult the <a class="xref" href="multi_contract-dsl.html" title="92. Contract DSL">Chapter 92, <i>Contract DSL</i></a> section for more information. We highly recommend using the map notation!</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>You must understand the map notation in order to set up contracts. Please read the
|
|
<a class="link" href="http://groovy-lang.org/json.html" target="_top">Groovy docs regarding JSON</a>.</p></td></tr></table></div><p>The previously shown contract is an agreement between two sides that:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><p class="simpara">if an HTTP request is sent with all of</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: circle; "><li class="listitem">a <code class="literal">PUT</code> method on the <code class="literal">/fraudcheck</code> endpoint,</li><li class="listitem">a JSON body with a <code class="literal">client.id</code> that matches the regular expression <code class="literal">[0-9]{10}</code> and
|
|
<code class="literal">loanAmount</code> equal to <code class="literal">99999</code>,</li><li class="listitem">and a <code class="literal">Content-Type</code> header with a value of <code class="literal">application/vnd.fraud.v1+json</code>,</li></ul></div></li><li class="listitem"><p class="simpara">then an HTTP response is sent to the consumer that</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: circle; "><li class="listitem">has status <code class="literal">200</code>,</li><li class="listitem">contains a JSON body with the <code class="literal">fraudCheckStatus</code> field containing a value <code class="literal">FRAUD</code> and
|
|
the <code class="literal">rejectionReason</code> field having value <code class="literal">Amount too high</code>,</li><li class="listitem">and a <code class="literal">Content-Type</code> header with a value of <code class="literal">application/vnd.fraud.v1+json</code>.</li></ul></div></li></ul></div><p>Once you are ready to check the API in practice in the integration tests, you need to
|
|
install the stubs locally.</p><p><span class="strong"><strong>Add the Spring Cloud Contract Verifier plugin.</strong></span></p><p>We can add either a Maven or a Gradle plugin. In this example, you see how to add Maven.
|
|
First, add the <code class="literal">Spring Cloud Contract</code> BOM.</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><dependencyManagement></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><dependencies></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><dependency></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><groupId></span>org.springframework.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-dependencies<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-release.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"><type></span>pom<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></type></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><scope></span>import<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></scope></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></dependency></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></dependencies></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></dependencyManagement></span></pre><p>Next, add the <code class="literal">Spring Cloud Contract Verifier</code> Maven plugin</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"><packageWithBaseClasses></span>com.example.fraud<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></packageWithBaseClasses></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>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 ---
|
|
[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 ---
|
|
[INFO] Building jar: /some/path/http-server/target/http-server-<span class="hl-number">0.0</span>.<span class="hl-number">1</span>-SNAPSHOT.jar
|
|
[INFO]
|
|
[INFO] --- spring-boot-maven-plugin:<span class="hl-number">1.5</span>.<span class="hl-number">5.</span>BUILD-SNAPSHOT:repackage (default) @ http-server ---
|
|
[INFO]
|
|
[INFO] --- maven-install-plugin:<span class="hl-number">2.5</span>.<span class="hl-number">2</span>:install (default-install) @ http-server ---
|
|
[INFO] Installing /some/path/http-server/target/http-server-<span class="hl-number">0.0</span>.<span class="hl-number">1</span>-SNAPSHOT.jar to /path/to/your/.m2/repository/com/example/http-server/<span class="hl-number">0.0</span>.<span class="hl-number">1</span>-SNAPSHOT/http-server-<span class="hl-number">0.0</span>.<span class="hl-number">1</span>-SNAPSHOT.jar
|
|
[INFO] Installing /some/path/http-server/pom.xml to /path/to/your/.m2/repository/com/example/http-server/<span class="hl-number">0.0</span>.<span class="hl-number">1</span>-SNAPSHOT/http-server-<span class="hl-number">0.0</span>.<span class="hl-number">1</span>-SNAPSHOT.pom
|
|
[INFO] Installing /some/path/http-server/target/http-server-<span class="hl-number">0.0</span>.<span class="hl-number">1</span>-SNAPSHOT-stubs.jar to /path/to/your/.m2/repository/com/example/http-server/<span class="hl-number">0.0</span>.<span class="hl-number">1</span>-SNAPSHOT/http-server-<span class="hl-number">0.0</span>.<span class="hl-number">1</span>-SNAPSHOT-stubs.jar</pre><p>The following line is extremely important:</p><pre class="programlisting">[INFO] Installing /some/path/http-server/target/http-server-<span class="hl-number">0.0</span>.<span class="hl-number">1</span>-SNAPSHOT-stubs.jar to /path/to/your/.m2/repository/com/example/http-server/<span class="hl-number">0.0</span>.<span class="hl-number">1</span>-SNAPSHOT/http-server-<span class="hl-number">0.0</span>.<span class="hl-number">1</span>-SNAPSHOT-stubs.jar</pre><p>It confirms that the stubs of the <code class="literal">http-server</code> have been installed in the local
|
|
repository.</p><p><span class="strong"><strong>Run the integration tests.</strong></span></p><p>In order to profit from the Spring Cloud Contract Stub Runner functionality of automatic
|
|
stub downloading, you must do the following in your consumer side project (<code class="literal">Loan
|
|
Application service</code>):</p><p>Add the <code class="literal">Spring Cloud Contract</code> BOM:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><dependencyManagement></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><dependencies></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><dependency></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><groupId></span>org.springframework.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-dependencies<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-release-train.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"><type></span>pom<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></type></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><scope></span>import<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></scope></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></dependency></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></dependencies></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></dependencyManagement></span></pre><p>Add the dependency to <code class="literal">Spring Cloud Contract Stub Runner</code>:</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-stub-runner<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></artifactId></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><scope></span>test<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></scope></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></dependency></span></pre><p>Annotate your test class with <code class="literal">@AutoConfigureStubRunner</code>. In the annotation, provide the
|
|
<code class="literal">group-id</code> and <code class="literal">artifact-id</code> for the Stub Runner to download the stubs of your
|
|
collaborators. (Optional step) Because you’re playing with the collaborators offline, you
|
|
can also provide the offline work switch (<code class="literal">StubRunnerProperties.StubsMode.LOCAL</code>).</p><pre class="programlisting"><em><span class="hl-annotation" style="color: gray">@RunWith(SpringRunner.class)</span></em>
|
|
<em><span class="hl-annotation" style="color: gray">@SpringBootTest(webEnvironment=WebEnvironment.NONE)</span></em>
|
|
<em><span class="hl-annotation" style="color: gray">@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:6565"},
|
|
stubsMode = StubRunnerProperties.StubsMode.LOCAL)</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">class</span> LoanApplicationServiceTests {</pre><p>Now, when you run your tests, you see something like this:</p><pre class="programlisting"><span class="hl-number">2016</span>-<span class="hl-number">07</span>-<span class="hl-number">19</span> <span class="hl-number">14</span>:<span class="hl-number">22</span>:<span class="hl-number">25.403</span> INFO <span class="hl-number">41050</span> --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Desired version is + - will try to resolve the latest version
|
|
<span class="hl-number">2016</span>-<span class="hl-number">07</span>-<span class="hl-number">19</span> <span class="hl-number">14</span>:<span class="hl-number">22</span>:<span class="hl-number">25.438</span> INFO <span class="hl-number">41050</span> --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Resolved version is <span class="hl-number">0.0</span>.<span class="hl-number">1</span>-SNAPSHOT
|
|
<span class="hl-number">2016</span>-<span class="hl-number">07</span>-<span class="hl-number">19</span> <span class="hl-number">14</span>:<span class="hl-number">22</span>:<span class="hl-number">25.439</span> INFO <span class="hl-number">41050</span> --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Resolving artifact com.example:http-server:jar:stubs:<span class="hl-number">0.0</span>.<span class="hl-number">1</span>-SNAPSHOT using remote repositories []
|
|
<span class="hl-number">2016</span>-<span class="hl-number">07</span>-<span class="hl-number">19</span> <span class="hl-number">14</span>:<span class="hl-number">22</span>:<span class="hl-number">25.451</span> INFO <span class="hl-number">41050</span> --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Resolved artifact com.example:http-server:jar:stubs:<span class="hl-number">0.0</span>.<span class="hl-number">1</span>-SNAPSHOT to /path/to/your/.m2/repository/com/example/http-server/<span class="hl-number">0.0</span>.<span class="hl-number">1</span>-SNAPSHOT/http-server-<span class="hl-number">0.0</span>.<span class="hl-number">1</span>-SNAPSHOT-stubs.jar
|
|
<span class="hl-number">2016</span>-<span class="hl-number">07</span>-<span class="hl-number">19</span> <span class="hl-number">14</span>:<span class="hl-number">22</span>:<span class="hl-number">25.465</span> INFO <span class="hl-number">41050</span> --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Unpacking stub from JAR [URI: file:/path/to/your/.m2/repository/com/example/http-server/<span class="hl-number">0.0</span>.<span class="hl-number">1</span>-SNAPSHOT/http-server-<span class="hl-number">0.0</span>.<span class="hl-number">1</span>-SNAPSHOT-stubs.jar]
|
|
<span class="hl-number">2016</span>-<span class="hl-number">07</span>-<span class="hl-number">19</span> <span class="hl-number">14</span>:<span class="hl-number">22</span>:<span class="hl-number">25.475</span> INFO <span class="hl-number">41050</span> --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Unpacked file to [/var/folders/<span class="hl-number">0</span>p/xwq47sq106x1_g3dtv6qfm940000gq/T/contracts100276532569594265]
|
|
<span class="hl-number">2016</span>-<span class="hl-number">07</span>-<span class="hl-number">19</span> <span class="hl-number">14</span>:<span class="hl-number">22</span>:<span class="hl-number">27.737</span> INFO <span class="hl-number">41050</span> --- [ main] o.s.c.c.stubrunner.StubRunnerExecutor : All stubs are now running RunningStubs [namesAndPorts={com.example:http-server:<span class="hl-number">0.0</span>.<span class="hl-number">1</span>-SNAPSHOT:stubs=<span class="hl-number">8080</span>}]</pre><p>This output means that Stub Runner has found your stubs and started a server for your app
|
|
with group id <code class="literal">com.example</code>, artifact id <code class="literal">http-server</code> with version <code class="literal">0.0.1-SNAPSHOT</code> of
|
|
the stubs and with <code class="literal">stubs</code> classifier on port <code class="literal">8080</code>.</p><p><span class="strong"><strong>File a pull request.</strong></span></p><p>What you have done until now is an iterative process. You can play around with the
|
|
contract, install it locally, and work on the consumer side until the contract works as
|
|
you wish.</p><p>Once you are satisfied with the results and the test passes, publish a pull request to
|
|
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>86.5.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>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><groupId></span>org.springframework.cloud<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></groupId></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><artifactId></span>spring-cloud-starter-contract-verifier<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></artifactId></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><scope></span>test<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></scope></span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></dependency></span></pre><p>In the configuration of the Maven plugin, pass the <code class="literal">packageWithBaseClasses</code> property</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"><packageWithBaseClasses></span>com.example.fraud<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></packageWithBaseClasses></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><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 example uses "convention based" naming by setting the
|
|
<code class="literal">packageWithBaseClasses</code> property. Doing so means that the two last packages combine to
|
|
make the name of the base test class. In our case, the contracts were placed under
|
|
<code class="literal">src/test/resources/contracts/fraud</code>. Since you do not have two packages starting from
|
|
the <code class="literal">contracts</code> folder, pick only one, which should be <code class="literal">fraud</code>. Add the <code class="literal">Base</code> suffix and
|
|
capitalize <code class="literal">fraud</code>. That gives you the <code class="literal">FraudBase</code> test class name.</p></td></tr></table></div><p>All the generated tests extend that class. Over there, you can set up your Spring Context
|
|
or whatever is necessary. In this case, use <a class="link" href="http://rest-assured.io/" target="_top">Rest Assured MVC</a> to
|
|
start the server side <code class="literal">FraudDetectionController</code>.</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">package</span> com.example.fraud;
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> org.junit.Before;
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">import</span> io.restassured.module.mockmvc.RestAssuredMockMvc;
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span> FraudBase {
|
|
<em><span class="hl-annotation" style="color: gray">@Before</span></em>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">void</span> setup() {
|
|
RestAssuredMockMvc.standaloneSetup(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> FraudDetectionController(),
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> FraudStatsController(stubbedStatsProvider()));
|
|
}
|
|
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">private</span> StatsProvider stubbedStatsProvider() {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> fraudType -> {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">switch</span> (fraudType) {
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">case</span> DRUNKS:
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> <span class="hl-number">100</span>;
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">case</span> ALL:
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> <span class="hl-number">200</span>;
|
|
}
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> <span class="hl-number">0</span>;
|
|
};
|
|
}
|
|
|
|
<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> assertThatRejectionReasonIsNull(Object rejectionReason) {
|
|
assert rejectionReason == null;
|
|
}
|
|
}</pre><p>Now, if you run the <code class="literal">./mvnw clean install</code>, you get something like this:</p><pre class="programlisting">Results :
|
|
|
|
Tests in error:
|
|
ContractVerifierTest.validate_shouldMarkClientAsFraud:<span class="hl-number">32</span> » IllegalState Parsed...</pre><p>This error occurs because you have a new contract from which a test was generated and it
|
|
failed since you have not implemented the feature. The auto-generated test would look
|
|
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()
|
|
.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.fraud.v1+json"</span>)
|
|
.body(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"{\"client.id\":\"1234567890\",\"loanAmount\":99999}"</span>);
|
|
|
|
<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">"/fraudcheck"</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/vnd.fraud.v1.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">"['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>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
|
|
<code class="literal">refactor</code>. It is time to convert the <code class="literal">red</code> into the <code class="literal">green</code>.</p><p><span class="strong"><strong>Write the missing implementation.</strong></span></p><p>Because you know the expected input and expected output, you can write the missing
|
|
implementation:</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">if</span> (amountGreaterThanThreshold(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.FRAUD, AMOUNT_TOO_HIGH);
|
|
}
|
|
<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>86.5.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
|
|
achieving the same thing by changing the properties.</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute">stubrunner</span>:
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute"> ids</span>: <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'com.example:http-server-dsl:+:stubs:8080'</span>
|
|
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute"> repositoryRoot</span>: http://repo.spring.io/libs-snapshot</pre><p>That’s it!</p></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_dependencies" href="#_dependencies"></a>86.6 Dependencies</h2></div></div></div><p>The best way to add dependencies is to use the proper <code class="literal">starter</code> dependency.</p><p>For <code class="literal">stub-runner</code>, use <code class="literal">spring-cloud-starter-stub-runner</code>. When you use a plugin, add
|
|
<code class="literal">spring-cloud-starter-contract-verifier</code>.</p></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_additional_links" href="#_additional_links"></a>86.7 Additional Links</h2></div></div></div><p>Here are some resources related to Spring Cloud Contract Verifier and Stub Runner. Note
|
|
that some may be outdated, because the Spring Cloud Contract Verifier project is under
|
|
constant development.</p><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_spring_cloud_contract_video" href="#_spring_cloud_contract_video"></a>86.7.1 Spring Cloud Contract video</h3></div></div></div><p>You can check out the video from the Warsaw JUG about Spring Cloud Contract:</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_readings" href="#_readings"></a>86.7.2 Readings</h3></div></div></div><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><a class="link" href="http://www.slideshare.net/MarcinGrzejszczak/stick-to-the-rules-consumer-driven-contracts-201507-confitura" target="_top">Slides from Marcin Grzejszczak’s talk about Accurest</a></li><li class="listitem"><a class="link" href="http://toomuchcoding.com/blog/categories/accurest/" target="_top">Accurest related articles from Marcin Grzejszczak’s blog</a></li><li class="listitem"><a class="link" href="http://toomuchcoding.com/blog/categories/spring-cloud-contract/" target="_top">Spring Cloud Contract related articles from Marcin Grzejszczak’s blog</a></li><li class="listitem"><a class="link" href="http://groovy-lang.org/json.html" target="_top">Groovy docs regarding JSON</a></li></ul></div></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_samples_2" href="#_samples_2"></a>86.8 Samples</h2></div></div></div><p>You can find some samples at
|
|
<a class="link" href="https://github.com/spring-cloud-samples/spring-cloud-contract-samples" target="_top">samples</a>.</p></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="multi__spring_cloud_contract_2.html">Prev</a> </td><td width="20%" align="center"><a accesskey="u" href="multi__spring_cloud_contract.html">Up</a></td><td width="40%" align="right"> <a accesskey="n" href="multi__spring_cloud_contract_faq.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">85. Spring Cloud Contract </td><td width="20%" align="center"><a accesskey="h" href="multi_spring-cloud.html">Home</a></td><td width="40%" align="right" valign="top"> 87. Spring Cloud Contract FAQ</td></tr></table></div></body></html> |