|
|
|
|
@@ -440,6 +440,7 @@ body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-b
|
|
|
|
|
<ul class="sectlevel4">
|
|
|
|
|
<li><a href="#_consumer_side_loan_issuance">Consumer side (Loan Issuance)</a></li>
|
|
|
|
|
<li><a href="#_producer_side_fraud_detection_server">Producer side (Fraud Detection server)</a></li>
|
|
|
|
|
<li><a href="#_consumer_side_loan_issuance_final_step">Consumer side (Loan Issuance) final step</a></li>
|
|
|
|
|
</ul>
|
|
|
|
|
</li>
|
|
|
|
|
<li><a href="#_dependencies">Dependencies</a></li>
|
|
|
|
|
@@ -921,7 +922,7 @@ for response verification.</p>
|
|
|
|
|
going through the process. CDC is all about communication.</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
<p>The <a href="https://github.com/spring-cloud/spring-cloud-contract/tree/master/samples/standalone/http-server">server side code is available here</a> and <a href="https://github.com/spring-cloud/spring-cloud-contract/tree/master/samples/standalone/http-client">the client side code here</a>.</p>
|
|
|
|
|
<p>The <a href="https://github.com/spring-cloud/spring-cloud-contract/tree/master/samples/standalone/dsl/http-server">server side code is available here</a> and <a href="https://github.com/spring-cloud/spring-cloud-contract/tree/master/samples/standalone/dsl/http-client">the client side code here</a>.</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="sect4">
|
|
|
|
|
<h5 id="_consumer_side_loan_issuance">Consumer side (Loan Issuance)</h5>
|
|
|
|
|
@@ -933,7 +934,18 @@ going through the process. CDC is all about communication.</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="listingblock">
|
|
|
|
|
<div class="content">
|
|
|
|
|
<pre class="highlight"><code class="language-groovy" data-lang="groovy">Unresolved directive in verifier/introduction.adoc - include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-contract/master/samples/standalone/http-client/src/test/java/com/example/loan/LoanApplicationServiceTests.java[tags=client_tdd,indent=0]</code></pre>
|
|
|
|
|
<pre class="highlight"><code class="language-groovy" data-lang="groovy">@Test
|
|
|
|
|
public void shouldBeRejectedDueToAbnormalLoanAmount() {
|
|
|
|
|
// given:
|
|
|
|
|
LoanApplication application = new LoanApplication(new Client("1234567890"),
|
|
|
|
|
99999);
|
|
|
|
|
// when:
|
|
|
|
|
LoanApplicationResult loanApplication = service.loanApplication(application);
|
|
|
|
|
// then:
|
|
|
|
|
assertThat(loanApplication.getLoanApplicationStatus())
|
|
|
|
|
.isEqualTo(LoanApplicationStatus.LOAN_APPLICATION_REJECTED);
|
|
|
|
|
assertThat(loanApplication.getRejectionReason()).isEqualTo("Amount too high");
|
|
|
|
|
}</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
@@ -947,7 +959,10 @@ going through the process. CDC is all about communication.</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="listingblock">
|
|
|
|
|
<div class="content">
|
|
|
|
|
<pre class="highlight"><code class="language-groovy" data-lang="groovy">Unresolved directive in verifier/introduction.adoc - include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-contract/master/samples/standalone/http-client/src/main/java/com/example/loan/LoanApplicationService.java[tags=client_call_server,indent=0]</code></pre>
|
|
|
|
|
<pre class="highlight"><code class="language-groovy" data-lang="groovy">ResponseEntity<FraudServiceResponse> response =
|
|
|
|
|
restTemplate.exchange("http://localhost:" + port + "/fraudcheck", HttpMethod.PUT,
|
|
|
|
|
new HttpEntity<>(request, httpHeaders),
|
|
|
|
|
FraudServiceResponse.class);</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
@@ -975,142 +990,276 @@ going through the process. CDC is all about communication.</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="listingblock">
|
|
|
|
|
<div class="content">
|
|
|
|
|
<pre class="highlight"><code class="language-groovy" data-lang="groovy">Unresolved directive in verifier/introduction.adoc - include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-contract/master/samples/standalone/http-server/src/test/resources/contracts/shouldMarkClientAsFraud.groovy[]
|
|
|
|
|
<pre class="highlight"><code class="language-groovy" data-lang="groovy">package contracts
|
|
|
|
|
|
|
|
|
|
The Contract is written using a statically typed Groovy DSL. You might be wondering what are those
|
|
|
|
|
`value(client(...), server(...))` parts. By using this notation Spring Cloud Contract allows you to
|
|
|
|
|
org.springframework.cloud.contract.spec.Contract.make {
|
|
|
|
|
request { // (1)
|
|
|
|
|
method 'PUT' // (2)
|
|
|
|
|
url '/fraudcheck' // (3)
|
|
|
|
|
body([ // (4)
|
|
|
|
|
clientId: value(consumer(regex('[0-9]{10}'))),
|
|
|
|
|
loanAmount: 99999
|
|
|
|
|
])
|
|
|
|
|
headers { // (5)
|
|
|
|
|
header('Content-Type', 'application/vnd.fraud.v1+json')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
response { // (6)
|
|
|
|
|
status 200 // (7)
|
|
|
|
|
body([ // (8)
|
|
|
|
|
fraudCheckStatus: "FRAUD",
|
|
|
|
|
rejectionReason: "Amount too high"
|
|
|
|
|
])
|
|
|
|
|
headers { // (9)
|
|
|
|
|
header('Content-Type': value(
|
|
|
|
|
producer(regex('application/vnd.fraud.v1.json.*')),
|
|
|
|
|
consumer('application/vnd.fraud.v1+json'))
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
Since we don't want to force on the user to hardcode values of fields that are dynamic
|
|
|
|
|
(timestamps, database ids etc.), one can provide parametrize those entries by using the
|
|
|
|
|
`value(consumer(...), producer(...))` method. That way what's present in the `consumer`
|
|
|
|
|
section will end up in the produced stub. What's there in the `producer` will end up in the
|
|
|
|
|
autogenerated test. If you provide only the regular expression side without the concrete
|
|
|
|
|
value then Spring Cloud Contract will generate one for you.
|
|
|
|
|
|
|
|
|
|
From the Consumer perspective, when shooting a request in the integration test:
|
|
|
|
|
|
|
|
|
|
(1) - If the consumer sends a request
|
|
|
|
|
(2) - With the "PUT" method
|
|
|
|
|
(3) - to the URL "/fraudcheck"
|
|
|
|
|
(4) - with the JSON body that
|
|
|
|
|
* has a field `clientId` 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/vnd.fraud.v1+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/vnd.fraud.v1+json`
|
|
|
|
|
|
|
|
|
|
From the Producer perspective, in the autogenerated producer-side test:
|
|
|
|
|
|
|
|
|
|
(1) - A request will be sent to the producer
|
|
|
|
|
(2) - With the "PUT" method
|
|
|
|
|
(3) - to the URL "/fraudcheck"
|
|
|
|
|
(4) - with the JSON body that
|
|
|
|
|
* has a field `clientId` 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/vnd.fraud.v1+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/vnd.fraud.v1+json.*`
|
|
|
|
|
*/</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
<p>The Contract is written using a statically typed Groovy DSL. You might be wondering what are those
|
|
|
|
|
<code>value(client(…​), server(…​))</code> parts. By using this notation Spring Cloud Contract allows you to
|
|
|
|
|
define parts of a JSON / URL / etc. which are dynamic. In case of an identifier or a timestamp you
|
|
|
|
|
don't want to hardcode a value. You want to allow some different ranges of values. That's why for
|
|
|
|
|
don’t want to hardcode a value. You want to allow some different ranges of values. That’s why for
|
|
|
|
|
the consumer side you can set regular expressions matching those values. You can provide the body
|
|
|
|
|
either by means of a map notation or String with interpolations.
|
|
|
|
|
https://cloud.spring.io/spring-cloud-contract/spring-cloud-contract.html#_contract_dsl[Consult the docs
|
|
|
|
|
for more information.] We highly recommend using the map notation!
|
|
|
|
|
|
|
|
|
|
The aforementioned contract is an agreement between two sides that:
|
|
|
|
|
|
|
|
|
|
- if an HTTP request is sent with
|
|
|
|
|
** a method `PUT` on an endpoint `/fraudcheck`
|
|
|
|
|
** JSON body with `clientId` matching the regular expression `[0-9]{10}` and `loanAmount` equal to `99999`
|
|
|
|
|
** and with a header `Content-Type` equal to `application/vnd.fraud.v1+json`
|
|
|
|
|
- then an HTTP response would be sent to the consumer that
|
|
|
|
|
** has status `200`
|
|
|
|
|
** contains JSON body with the `fraudCheckStatus` field containing a value `FRAUD` and the `rejectionReason` field having value `Amount too high`
|
|
|
|
|
** and a `Content-Type` header with a value of `application/vnd.fraud.v1+json`
|
|
|
|
|
|
|
|
|
|
Once we're ready to check the API in practice in the integration tests we need to just install the stubs locally
|
|
|
|
|
|
|
|
|
|
*add the Spring Cloud Contract Verifier plugin*
|
|
|
|
|
|
|
|
|
|
We can add either Maven or Gradle plugin - in this example we'll show how to add Maven. First we need to add the `Spring Cloud Contract` BOM.
|
|
|
|
|
|
|
|
|
|
[source,xml,indent=0]</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
<a href="https://cloud.spring.io/spring-cloud-contract/spring-cloud-contract.html#_contract_dsl">Consult the docs
|
|
|
|
|
for more information.</a> We highly recommend using the map notation!</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
<p>Unresolved directive in verifier/introduction.adoc - include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-contract/master/samples/standalone/http-server/pom.xml[tags=contract_bom,indent=0]</p>
|
|
|
|
|
<p>The aforementioned contract is an agreement between two sides that:</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="ulist">
|
|
|
|
|
<ul>
|
|
|
|
|
<li>
|
|
|
|
|
<p>if an HTTP request is sent with</p>
|
|
|
|
|
<div class="ulist">
|
|
|
|
|
<ul>
|
|
|
|
|
<li>
|
|
|
|
|
<p>a method <code>PUT</code> on an endpoint <code>/fraudcheck</code></p>
|
|
|
|
|
</li>
|
|
|
|
|
<li>
|
|
|
|
|
<p>JSON body with <code>clientId</code> matching the regular expression <code>[0-9]{10}</code> and <code>loanAmount</code> equal to <code>99999</code></p>
|
|
|
|
|
</li>
|
|
|
|
|
<li>
|
|
|
|
|
<p>and with a header <code>Content-Type</code> equal to <code>application/vnd.fraud.v1+json</code></p>
|
|
|
|
|
</li>
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
</li>
|
|
|
|
|
<li>
|
|
|
|
|
<p>then an HTTP response would be sent to the consumer that</p>
|
|
|
|
|
<div class="ulist">
|
|
|
|
|
<ul>
|
|
|
|
|
<li>
|
|
|
|
|
<p>has status <code>200</code></p>
|
|
|
|
|
</li>
|
|
|
|
|
<li>
|
|
|
|
|
<p>contains JSON body with the <code>fraudCheckStatus</code> field containing a value <code>FRAUD</code> and the <code>rejectionReason</code> field having value <code>Amount too high</code></p>
|
|
|
|
|
</li>
|
|
|
|
|
<li>
|
|
|
|
|
<p>and a <code>Content-Type</code> header with a value of <code>application/vnd.fraud.v1+json</code></p>
|
|
|
|
|
</li>
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
</li>
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
<p>Once we’re ready to check the API in practice in the integration tests we need to just install the stubs locally</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
<p><strong>add the Spring Cloud Contract Verifier plugin</strong></p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
<p>We can add either Maven or Gradle plugin - in this example we’ll show how to add Maven. First we need to add the <code>Spring Cloud Contract</code> BOM.</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="listingblock">
|
|
|
|
|
<div class="content">
|
|
|
|
|
<pre>Next, the `Spring Cloud Contract Verifier` Maven plugin
|
|
|
|
|
|
|
|
|
|
[source,xml,indent=0]</pre>
|
|
|
|
|
<pre class="highlight"><code class="language-xml" data-lang="xml"><dependencyManagement>
|
|
|
|
|
<dependencies>
|
|
|
|
|
<dependency>
|
|
|
|
|
<groupId>org.springframework.cloud</groupId>
|
|
|
|
|
<artifactId>spring-cloud-contract-dependencies</artifactId>
|
|
|
|
|
<version>${spring-cloud-contract.version}</version>
|
|
|
|
|
<type>pom</type>
|
|
|
|
|
<scope>import</scope>
|
|
|
|
|
</dependency>
|
|
|
|
|
</dependencies>
|
|
|
|
|
</dependencyManagement></code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
<p>Unresolved directive in verifier/introduction.adoc - include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-contract/master/samples/standalone/http-server/pom.xml[tags=contract_maven_plugin,indent=0]</p>
|
|
|
|
|
<p>Next, the <code>Spring Cloud Contract Verifier</code> Maven plugin</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="listingblock">
|
|
|
|
|
<div class="content">
|
|
|
|
|
<pre>Since the plugin was added we get the `Spring Cloud Contract Verifier` features which from the provided contracts:
|
|
|
|
|
|
|
|
|
|
- generate and run tests
|
|
|
|
|
- produce and install stubs
|
|
|
|
|
|
|
|
|
|
We don't want to generate tests since we, as consumers, want only to play with the stubs. That's why we need to skip the tests generation and execution. When we execute:
|
|
|
|
|
|
|
|
|
|
[source,bash,indent=0]</pre>
|
|
|
|
|
<pre class="highlight"><code class="language-xml" data-lang="xml"><plugin>
|
|
|
|
|
<groupId>org.springframework.cloud</groupId>
|
|
|
|
|
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
|
|
|
|
|
<version>${spring-cloud-contract.version}</version>
|
|
|
|
|
<extensions>true</extensions>
|
|
|
|
|
<configuration>
|
|
|
|
|
<baseClassForTests>com.example.fraud.MvcTest</baseClassForTests>
|
|
|
|
|
</configuration>
|
|
|
|
|
</plugin></code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
<p>cd local-http-server-repo
|
|
|
|
|
./mvnw clean install -DskipTests</p>
|
|
|
|
|
<p>Since the plugin was added we get the <code>Spring Cloud Contract Verifier</code> features which from the provided contracts:</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="ulist">
|
|
|
|
|
<ul>
|
|
|
|
|
<li>
|
|
|
|
|
<p>generate and run tests</p>
|
|
|
|
|
</li>
|
|
|
|
|
<li>
|
|
|
|
|
<p>produce and install stubs</p>
|
|
|
|
|
</li>
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
<p>We don’t want to generate tests since we, as consumers, want only to play with the stubs. That’s why we need to skip the tests generation and execution. When we execute:</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="listingblock">
|
|
|
|
|
<div class="content">
|
|
|
|
|
<pre>In the logs we'll see something like this:
|
|
|
|
|
|
|
|
|
|
[source,bash,indent=0]</pre>
|
|
|
|
|
<pre class="highlight"><code class="language-bash" data-lang="bash">cd local-http-server-repo
|
|
|
|
|
./mvnw clean install -DskipTests</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
<p>[INFO] --- spring-cloud-contract-maven-plugin:1.0.0.BUILD-SNAPSHOT:generateStubs (default-generateStubs) @ http-server ---
|
|
|
|
|
[INFO] Building jar: /some/path/http-server/target/http-server-0.0.1-SNAPSHOT-stubs.jar</p>
|
|
|
|
|
<p>In the logs we’ll see something like this:</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
<p>[INFO] --- maven-jar-plugin:2.6:jar (default-jar) @ http-server ---
|
|
|
|
|
[INFO] Building jar: /some/path/http-server/target/http-server-0.0.1-SNAPSHOT.jar</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
<p>[INFO] --- spring-boot-maven-plugin:1.4.0.BUILD-SNAPSHOT:repackage (default) @ http-server ---</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
<p>[INFO] --- maven-install-plugin:2.5.2:install (default-install) @ http-server ---
|
|
|
|
|
<div class="listingblock">
|
|
|
|
|
<div class="content">
|
|
|
|
|
<pre class="highlight"><code class="language-bash" data-lang="bash">[INFO] --- spring-cloud-contract-maven-plugin:1.0.0.BUILD-SNAPSHOT:generateStubs (default-generateStubs) @ http-server ---
|
|
|
|
|
[INFO] Building jar: /some/path/http-server/target/http-server-0.0.1-SNAPSHOT-stubs.jar
|
|
|
|
|
[INFO]
|
|
|
|
|
[INFO] --- maven-jar-plugin:2.6:jar (default-jar) @ http-server ---
|
|
|
|
|
[INFO] Building jar: /some/path/http-server/target/http-server-0.0.1-SNAPSHOT.jar
|
|
|
|
|
[INFO]
|
|
|
|
|
[INFO] --- spring-boot-maven-plugin:1.4.0.BUILD-SNAPSHOT:repackage (default) @ http-server ---
|
|
|
|
|
[INFO]
|
|
|
|
|
[INFO] --- maven-install-plugin:2.5.2:install (default-install) @ http-server ---
|
|
|
|
|
[INFO] Installing /some/path/http-server/target/http-server-0.0.1-SNAPSHOT.jar to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT.jar
|
|
|
|
|
[INFO] Installing /some/path/http-server/pom.xml to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT.pom
|
|
|
|
|
[INFO] Installing /some/path/http-server/target/http-server-0.0.1-SNAPSHOT-stubs.jar to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="listingblock">
|
|
|
|
|
<div class="content">
|
|
|
|
|
<pre>This line is extremely important
|
|
|
|
|
|
|
|
|
|
[source,bash,indent=0]</pre>
|
|
|
|
|
[INFO] Installing /some/path/http-server/target/http-server-0.0.1-SNAPSHOT-stubs.jar to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
<p>[INFO] Installing /some/path/http-server/target/http-server-0.0.1-SNAPSHOT-stubs.jar to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar</p>
|
|
|
|
|
<p>This line is extremely important</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="listingblock">
|
|
|
|
|
<div class="content">
|
|
|
|
|
<pre>It's confirming that the stubs of the `http-server` have been installed in the local repository.
|
|
|
|
|
|
|
|
|
|
*run the integration tests*
|
|
|
|
|
|
|
|
|
|
In order to profit from the Spring Cloud Contract Stub Runner functionality of automatic stub downloading you have to do the following in our consumer side project (`Loan Application service`).
|
|
|
|
|
|
|
|
|
|
Add the `Spring Cloud Contract` BOM
|
|
|
|
|
|
|
|
|
|
[source,xml,indent=0]</pre>
|
|
|
|
|
<pre class="highlight"><code class="language-bash" data-lang="bash">[INFO] Installing /some/path/http-server/target/http-server-0.0.1-SNAPSHOT-stubs.jar to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
<p>Unresolved directive in verifier/introduction.adoc - include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-contract/master/samples/standalone/http-client/pom.xml[tags=contract_bom,indent=0]</p>
|
|
|
|
|
<p>It’s confirming that the stubs of the <code>http-server</code> have been installed in the local repository.</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
<p><strong>run the integration tests</strong></p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
<p>In order to profit from the Spring Cloud Contract Stub Runner functionality of automatic stub downloading you have to do the following in our consumer side project (<code>Loan Application service</code>).</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
<p>Add the <code>Spring Cloud Contract</code> BOM</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="listingblock">
|
|
|
|
|
<div class="content">
|
|
|
|
|
<pre>Add the dependency to `Spring Cloud Contract Stub Runner`
|
|
|
|
|
|
|
|
|
|
[source,xml,indent=0]</pre>
|
|
|
|
|
<pre class="highlight"><code class="language-xml" data-lang="xml"><dependencyManagement>
|
|
|
|
|
<dependencies>
|
|
|
|
|
<dependency>
|
|
|
|
|
<groupId>org.springframework.cloud</groupId>
|
|
|
|
|
<artifactId>spring-cloud-contract-dependencies</artifactId>
|
|
|
|
|
<version>${spring-cloud-contract.version}</version>
|
|
|
|
|
<type>pom</type>
|
|
|
|
|
<scope>import</scope>
|
|
|
|
|
</dependency>
|
|
|
|
|
</dependencies>
|
|
|
|
|
</dependencyManagement></code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
<p>Unresolved directive in verifier/introduction.adoc - include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-contract/master/samples/standalone/http-client/pom.xml[tags=stub_runner,indent=0]</p>
|
|
|
|
|
<p>Add the dependency to <code>Spring Cloud Contract Stub Runner</code></p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="listingblock">
|
|
|
|
|
<div class="content">
|
|
|
|
|
<pre>Provide the group id and artifact id for the Stub Runner to download stubs of your collaborators. Also provide the offline work switch since you're playing with the collaborators offline (optional step).
|
|
|
|
|
|
|
|
|
|
[source,yaml,indent=0]</pre>
|
|
|
|
|
<pre class="highlight"><code class="language-xml" data-lang="xml"><dependency>
|
|
|
|
|
<groupId>org.springframework.cloud</groupId>
|
|
|
|
|
<artifactId>spring-cloud-contract-wiremock</artifactId>
|
|
|
|
|
<scope>test</scope>
|
|
|
|
|
</dependency>
|
|
|
|
|
<dependency>
|
|
|
|
|
<groupId>org.springframework.cloud</groupId>
|
|
|
|
|
<artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
|
|
|
|
|
<scope>test</scope>
|
|
|
|
|
</dependency></code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
<p>Unresolved directive in verifier/introduction.adoc - include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-contract/master/samples/standalone/http-client/src/test/resources/application.yaml[]</p>
|
|
|
|
|
<p>Provide the group id and artifact id for the Stub Runner to download stubs of your collaborators. Also provide the offline work switch since you’re playing with the collaborators offline (optional step).</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="listingblock">
|
|
|
|
|
<div class="content">
|
|
|
|
|
<pre class="highlight"><code class="language-yaml" data-lang="yaml">stubrunner:
|
|
|
|
|
work-offline: true
|
|
|
|
|
stubs.ids: 'com.example:http-server-dsl:+:stubs:8080'</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
<p>Annotate your test class with <code>@AutoConfigureStubRunner</code></p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="listingblock">
|
|
|
|
|
<div class="content">
|
|
|
|
|
<pre class="highlight"><code class="language-groovy" data-lang="groovy">Unresolved directive in verifier/introduction.adoc - include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-contract/master/samples/standalone/http-client/src/test/java/com/example/loan/LoanApplicationServiceTests.java[tags=autoconfigure_stubrunner,indent=0]</code></pre>
|
|
|
|
|
<pre class="highlight"><code class="language-groovy" data-lang="groovy">@RunWith(SpringRunner.class)
|
|
|
|
|
@SpringBootTest
|
|
|
|
|
@AutoConfigureStubRunner
|
|
|
|
|
public class LoanApplicationServiceTests {</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
@@ -1153,8 +1302,13 @@ Add the `Spring Cloud Contract` BOM
|
|
|
|
|
</div>
|
|
|
|
|
<div class="listingblock">
|
|
|
|
|
<div class="content">
|
|
|
|
|
<pre class="highlight"><code class="language-java" data-lang="java">Unresolved directive in verifier/introduction.adoc - include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-contract/master/samples/standalone/http-server/src/main/java/com/example/fraud/FraudDetectionController.java[tags=server_api,indent=0]
|
|
|
|
|
Unresolved directive in verifier/introduction.adoc - include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-contract/master/samples/standalone/http-server/src/main/java/com/example/fraud/FraudDetectionController.java[tags=initial_impl,indent=0]
|
|
|
|
|
<pre class="highlight"><code class="language-java" data-lang="java">@RequestMapping(
|
|
|
|
|
value = "/fraudcheck",
|
|
|
|
|
method = PUT,
|
|
|
|
|
consumes = FRAUD_SERVICE_JSON_VERSION_1,
|
|
|
|
|
produces = FRAUD_SERVICE_JSON_VERSION_1)
|
|
|
|
|
public FraudCheckResult fraudCheck(@RequestBody FraudCheck fraudCheck) {
|
|
|
|
|
return new FraudCheckResult(FraudCheckStatus.OK, NO_REASON);
|
|
|
|
|
}</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
@@ -1172,7 +1326,11 @@ git pull https://your-git-server.com/server-side-fork.git contract-change-pr</co
|
|
|
|
|
</div>
|
|
|
|
|
<div class="listingblock">
|
|
|
|
|
<div class="content">
|
|
|
|
|
<pre class="highlight"><code class="language-xml" data-lang="xml">Unresolved directive in verifier/introduction.adoc - include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-contract/master/samples/standalone/http-server/pom.xml[tags=verifier_test_dependencies,indent=0]</code></pre>
|
|
|
|
|
<pre class="highlight"><code class="language-xml" data-lang="xml"><dependency>
|
|
|
|
|
<groupId>org.springframework.cloud</groupId>
|
|
|
|
|
<artifactId>spring-cloud-starter-contract-verifier</artifactId>
|
|
|
|
|
<scope>test</scope>
|
|
|
|
|
</dependency></code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
@@ -1180,7 +1338,15 @@ git pull https://your-git-server.com/server-side-fork.git contract-change-pr</co
|
|
|
|
|
</div>
|
|
|
|
|
<div class="listingblock">
|
|
|
|
|
<div class="content">
|
|
|
|
|
<pre class="highlight"><code class="language-xml" data-lang="xml">Unresolved directive in verifier/introduction.adoc - include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-contract/master/samples/standalone/http-server/pom.xml[tags=contract_maven_plugin,indent=0]</code></pre>
|
|
|
|
|
<pre class="highlight"><code class="language-xml" data-lang="xml"><plugin>
|
|
|
|
|
<groupId>org.springframework.cloud</groupId>
|
|
|
|
|
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
|
|
|
|
|
<version>${spring-cloud-contract.version}</version>
|
|
|
|
|
<extensions>true</extensions>
|
|
|
|
|
<configuration>
|
|
|
|
|
<baseClassForTests>com.example.fraud.MvcTest</baseClassForTests>
|
|
|
|
|
</configuration>
|
|
|
|
|
</plugin></code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
@@ -1188,117 +1354,136 @@ git pull https://your-git-server.com/server-side-fork.git contract-change-pr</co
|
|
|
|
|
</div>
|
|
|
|
|
<div class="listingblock">
|
|
|
|
|
<div class="content">
|
|
|
|
|
<pre class="highlight"><code class="language-java" data-lang="java">Unresolved directive in verifier/introduction.adoc - include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-contract/master/samples/standalone/http-server/src/test/java/com/example/fraud/MvcTest.java[]
|
|
|
|
|
<pre class="highlight"><code class="language-java" data-lang="java">package com.example.fraud;
|
|
|
|
|
|
|
|
|
|
Now, if you run the `./mvnw clean install` you would get sth like this:
|
|
|
|
|
import com.example.fraud.FraudDetectionController;
|
|
|
|
|
import com.jayway.restassured.module.mockmvc.RestAssuredMockMvc;
|
|
|
|
|
|
|
|
|
|
[source,bash,indent=0]</code></pre>
|
|
|
|
|
import org.junit.Before;
|
|
|
|
|
|
|
|
|
|
public class MvcTest {
|
|
|
|
|
|
|
|
|
|
@Before
|
|
|
|
|
public void setup() {
|
|
|
|
|
RestAssuredMockMvc.standaloneSetup(new FraudDetectionController());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void assertThatRejectionReasonIsNull(Object rejectionReason) {
|
|
|
|
|
assert rejectionReason == null;
|
|
|
|
|
}
|
|
|
|
|
}</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
<p>Results :</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
<p>Tests in error:
|
|
|
|
|
ContractVerifierTest.validate_shouldMarkClientAsFraud:32 » IllegalState Parsed…​</p>
|
|
|
|
|
<p>Now, if you run the <code>./mvnw clean install</code> you would get sth like this:</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="listingblock">
|
|
|
|
|
<div class="content">
|
|
|
|
|
<pre>That's because you have a new contract from which a test was generated and it failed since you haven't implemented the feature. The autogenerated test would look like this:
|
|
|
|
|
<pre class="highlight"><code class="language-bash" data-lang="bash">Results :
|
|
|
|
|
|
|
|
|
|
[source,java,indent=0]</pre>
|
|
|
|
|
Tests in error:
|
|
|
|
|
ContractVerifierTest.validate_shouldMarkClientAsFraud:32 » IllegalState Parsed...</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
<p>@Test
|
|
|
|
|
<p>That’s because you have a new contract from which a test was generated and it failed since you haven’t implemented the feature. The autogenerated test would look like this:</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="listingblock">
|
|
|
|
|
<div class="content">
|
|
|
|
|
<pre class="highlight"><code class="language-java" data-lang="java">@Test
|
|
|
|
|
public void validate_shouldMarkClientAsFraud() throws Exception {
|
|
|
|
|
// given:
|
|
|
|
|
MockMvcRequestSpecification request = given()
|
|
|
|
|
.header("Content-Type", "application/vnd.fraud.v1+json")
|
|
|
|
|
.body("{\"clientId\":\"1234567890\",\"loanAmount\":99999}");</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="literalblock">
|
|
|
|
|
<div class="content">
|
|
|
|
|
<pre>// when:
|
|
|
|
|
ResponseOptions response = given().spec(request)
|
|
|
|
|
.put("/fraudcheck");</pre>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="literalblock">
|
|
|
|
|
<div class="content">
|
|
|
|
|
<pre> // then:
|
|
|
|
|
.body("{\"clientId\":\"1234567890\",\"loanAmount\":99999}");
|
|
|
|
|
|
|
|
|
|
// when:
|
|
|
|
|
ResponseOptions response = given().spec(request)
|
|
|
|
|
.put("/fraudcheck");
|
|
|
|
|
|
|
|
|
|
// then:
|
|
|
|
|
assertThat(response.statusCode()).isEqualTo(200);
|
|
|
|
|
assertThat(response.header("Content-Type")).matches("application/vnd.fraud.v1.json.*");
|
|
|
|
|
// and:
|
|
|
|
|
DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());
|
|
|
|
|
assertThatJson(parsedJson).field("fraudCheckStatus").matches("[A-Z]{5}");
|
|
|
|
|
assertThatJson(parsedJson).field("rejectionReason").isEqualTo("Amount too high");
|
|
|
|
|
}</pre>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="listingblock">
|
|
|
|
|
<div class="content">
|
|
|
|
|
<pre>As you can see all the `producer()` parts of the Contract that were present in the `value(consumer(...), producer(...))` blocks got injected into the test.
|
|
|
|
|
|
|
|
|
|
What's important here to note is that on the producer side we also are doing TDD. We have expectations in form of a test. This test is shooting a request to our own application to an URL, headers and body defined in the contract. It also is expecting very precisely defined values in the response. In other words you have is your `red` part of `red`, `green` and `refactor`. Time to convert the `red` into the `green`.
|
|
|
|
|
|
|
|
|
|
*write the missing implementation*
|
|
|
|
|
|
|
|
|
|
Now since we now what is the expected input and expected output let's write the missing implementation.
|
|
|
|
|
|
|
|
|
|
[source,java,indent=0]</pre>
|
|
|
|
|
}</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
<p>Unresolved directive in verifier/introduction.adoc - include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-contract/master/samples/standalone/http-server/src/main/java/com/example/fraud/FraudDetectionController.java[tags=server_api,indent=0]
|
|
|
|
|
Unresolved directive in verifier/introduction.adoc - include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-contract/master/samples/standalone/http-server/src/main/java/com/example/fraud/FraudDetectionController.java[tags=new_impl,indent=0]
|
|
|
|
|
Unresolved directive in verifier/introduction.adoc - include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-contract/master/samples/standalone/http-server/src/main/java/com/example/fraud/FraudDetectionController.java[tags=initial_impl,indent=0]
|
|
|
|
|
}</p>
|
|
|
|
|
<p>As you can see all the <code>producer()</code> parts of the Contract that were present in the <code>value(consumer(…​), producer(…​))</code> blocks got injected into the test.</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
<p>What’s important here to note is that on the producer side we also are doing TDD. We have expectations in form of a test. This test is shooting a request to our own application to an URL, headers and body defined in the contract. It also is expecting very precisely defined values in the response. In other words you have is your <code>red</code> part of <code>red</code>, <code>green</code> and <code>refactor</code>. Time to convert the <code>red</code> into the <code>green</code>.</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
<p><strong>write the missing implementation</strong></p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
<p>Now since we now what is the expected input and expected output let’s write the missing implementation.</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="listingblock">
|
|
|
|
|
<div class="content">
|
|
|
|
|
<pre>If we execute `./mvnw clean install` again the tests will pass. Since the `Spring Cloud Contract Verifier` plugin adds the tests to the `generated-test-sources` you can actually run those tests from your IDE.
|
|
|
|
|
|
|
|
|
|
*deploy your app*
|
|
|
|
|
|
|
|
|
|
Once you've finished your work it's time to deploy your change. First merge the branch
|
|
|
|
|
|
|
|
|
|
[source,bash,indent=0]</pre>
|
|
|
|
|
<pre class="highlight"><code class="language-java" data-lang="java">@RequestMapping(
|
|
|
|
|
value = "/fraudcheck",
|
|
|
|
|
method = PUT,
|
|
|
|
|
consumes = FRAUD_SERVICE_JSON_VERSION_1,
|
|
|
|
|
produces = FRAUD_SERVICE_JSON_VERSION_1)
|
|
|
|
|
public FraudCheckResult fraudCheck(@RequestBody FraudCheck fraudCheck) {
|
|
|
|
|
if (amountGreaterThanThreshold(fraudCheck)) {
|
|
|
|
|
return new FraudCheckResult(FraudCheckStatus.FRAUD, AMOUNT_TOO_HIGH);
|
|
|
|
|
}
|
|
|
|
|
return new FraudCheckResult(FraudCheckStatus.OK, NO_REASON);
|
|
|
|
|
}</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
<p>git checkout master
|
|
|
|
|
<p>If we execute <code>./mvnw clean install</code> again the tests will pass. Since the <code>Spring Cloud Contract Verifier</code> plugin adds the tests to the <code>generated-test-sources</code> you can actually run those tests from your IDE.</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
<p><strong>deploy your app</strong></p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
<p>Once you’ve finished your work it’s time to deploy your change. First merge the branch</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="listingblock">
|
|
|
|
|
<div class="content">
|
|
|
|
|
<pre class="highlight"><code class="language-bash" data-lang="bash">git checkout master
|
|
|
|
|
git merge --no-ff contract-change-pr
|
|
|
|
|
git push origin master</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="listingblock">
|
|
|
|
|
<div class="content">
|
|
|
|
|
<pre>Then we assume that your CI would run sth like `./mvnw clean deploy` which would publish both the application and the stub artifcats.
|
|
|
|
|
|
|
|
|
|
===== Consumer side (Loan Issuance) final step
|
|
|
|
|
|
|
|
|
|
As a developer of the Loan Issuance service (a consumer of the Fraud Detection server):
|
|
|
|
|
|
|
|
|
|
*merge branch to master*
|
|
|
|
|
|
|
|
|
|
[source,bash,indent=0]</pre>
|
|
|
|
|
git push origin master</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
<p>git checkout master
|
|
|
|
|
git merge --no-ff contract-change-pr</p>
|
|
|
|
|
<p>Then we assume that your CI would run sth like <code>./mvnw clean deploy</code> which would publish both the application and the stub artifcats.</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="sect4">
|
|
|
|
|
<h5 id="_consumer_side_loan_issuance_final_step">Consumer side (Loan Issuance) final step</h5>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
<p>As a developer of the Loan Issuance service (a consumer of the Fraud Detection server):</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
<p><strong>merge branch to master</strong></p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="listingblock">
|
|
|
|
|
<div class="content">
|
|
|
|
|
<pre>*work online*
|
|
|
|
|
|
|
|
|
|
Now you can disable the offline work for Spring Cloud Contract Stub Runner ad provide where the repository with your stubs is placed. At this moment the stubs of the server side will be automatically downloaded from Nexus / Artifactory.
|
|
|
|
|
|
|
|
|
|
[source,yaml,indent=0]</pre>
|
|
|
|
|
<pre class="highlight"><code class="language-bash" data-lang="bash">git checkout master
|
|
|
|
|
git merge --no-ff contract-change-pr</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
<p>Unresolved directive in verifier/introduction.adoc - include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-contract/master/samples/standalone/http-client/src/test/resources/application-test-repo.yaml[]</p>
|
|
|
|
|
<p><strong>work online</strong></p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
<p>Now you can disable the offline work for Spring Cloud Contract Stub Runner ad provide where the repository with your stubs is placed. At this moment the stubs of the server side will be automatically downloaded from Nexus / Artifactory.</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="listingblock">
|
|
|
|
|
<div class="content">
|
|
|
|
|
<pre class="highlight"><code class="language-yaml" data-lang="yaml">stubrunner.stubs:
|
|
|
|
|
ids: 'com.example:http-server-dsl:+:stubs:8080'
|
|
|
|
|
repositoryRoot: http://repo.spring.io/libs-snapshot</code></pre>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="paragraph">
|
|
|
|
|
<p>And that’s it!</p>
|
|
|
|
|
@@ -5115,7 +5300,7 @@ will generate a stub something like this:</p>
|
|
|
|
|
<pre class="highlight"><code class="language-json" data-lang="json">{
|
|
|
|
|
"request" : {
|
|
|
|
|
"url" : "/resource",
|
|
|
|
|
"method" : "PUT",
|
|
|
|
|
"method" : "POST",
|
|
|
|
|
"bodyPatterns" : [ {
|
|
|
|
|
"matchesJsonPath" : "$.id"
|
|
|
|
|
}]
|
|
|
|
|
@@ -5155,7 +5340,7 @@ number of different ways, including as described above using
|
|
|
|
|
</div>
|
|
|
|
|
<div id="footer">
|
|
|
|
|
<div id="footer-text">
|
|
|
|
|
Last updated 2016-07-21 17:17:43 BST
|
|
|
|
|
Last updated 2016-07-26 11:46:15 CEST
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</body>
|
|
|
|
|
|