Sync docs from master to gh-pages
This commit is contained in:
@@ -45,7 +45,7 @@ sides of the communication. You can pass the values:</p><p>Either via the <code
|
||||
value(stub(...), test(...))
|
||||
value(client(...), server(...))</pre><p>or using the <code class="literal">$()</code> method</p><pre class="programlisting">$(consumer(...), producer(...))
|
||||
$(stub(...), test(...))
|
||||
$(client(...), server(...))</pre><p>You can read more about this in the <a class="link" href="https://cloud.spring.io/spring-cloud-contract/spring-cloud-contract.html#_contract_dsl" target="_top">Contract DSL section</a>.</p><p>Calling <code class="literal">value()</code> or <code class="literal">$()</code> tells Spring Cloud Contract that you will be passing a dynamic value.
|
||||
$(client(...), server(...))</pre><p>You can read more about this in the <a class="link" href="https://cloud.spring.io/spring-cloud-contract/single/spring-cloud-contract.html#_contract_dsl" target="_top">Contract DSL section</a>.</p><p>Calling <code class="literal">value()</code> or <code class="literal">$()</code> tells Spring Cloud Contract that you will be passing a dynamic value.
|
||||
Inside the <code class="literal">consumer()</code> method you pass the value that should be used on the consumer side (in the generated stub).
|
||||
Inside the <code class="literal">producer()</code> method you pass the value that should be used on the producer side (in the generated test).</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>If on one side you have passed the regular expression and you haven’t passed the other, then the
|
||||
other side will get auto-generated.</p></td></tr></table></div><p>Most often you will use that method together with the <code class="literal">regex</code> helper method. E.g. <code class="literal">consumer(regex('[0-9]{10}'))</code>.</p><p>To sum it up the contract for the aforementioned scenario would look more or less like this (the regular expression
|
||||
|
||||
@@ -24,7 +24,197 @@ sides.</li><li class="listitem">To generate boilerplate test code to be used on
|
||||
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>2.3 How It Works</h2></div></div></div><p>This section explores how Spring Cloud Contract Verifier with Stub Runner works.</p><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_defining_the_contract" href="#_defining_the_contract"></a>2.3.1 Defining the contract</h3></div></div></div><p>As consumers of services, we need to define what exactly we want to achieve. We need to
|
||||
used to test contracts between applications and not to simulate full behavior.</p></td></tr></table></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_how_it_works" href="#_how_it_works"></a>2.3 How It Works</h2></div></div></div><p>This section explores how Spring Cloud Contract Verifier with Stub Runner works.</p><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_a_three_second_tour" href="#_a_three_second_tour"></a>2.3.1 A three second tour</h3></div></div></div><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="_on_the_producer_side" href="#_on_the_producer_side"></a>On the Producer Side</h4></div></div></div><p>In order to start working with <code class="literal">Spring Cloud Contract</code>, add files with REST/ messaging contracts expressed in either
|
||||
Groovy DSL or YAML to the contracts directory set by the
|
||||
<code class="literal">contractsDslDir</code> property, by default <code class="literal">$rootDir/src/test/resources/contracts</code>.</p><p>Then, add Spring Cloud Contract Verifier dependency and plugin to your build file:</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><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>Now, running <code class="literal">./mvnw clean install</code> will cause tests that verify the application
|
||||
compliance with the added contracts to be automatically generated, by default 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 will fail.</p><p>To make them pass, the correct implementation of either handling HTTP requests or messages
|
||||
will have to be added. Also, a correct base test class for auto-generated tests needs to be added to the project.
|
||||
This class will be 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 will pass, and both the application
|
||||
and the stub artifacts will be 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="_on_the_consumer_side" href="#_on_the_consumer_side"></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/
|
||||
messaging route that simulates the actual service.</p><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>Get the Producer-side stubs installed in your Maven repository by either:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">checking out the Producer side repository, adding contracts and generating the stubs by running:</li></ul></div><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 would fail;</p></td></tr></table></div><p>or:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">getting already existing producer service stubs from a remote repository; to do this, simply pass the
|
||||
stub artifact ids and artifact repository url as <code class="literal">Spring Cloud Contract Stub Runner</code> properties:</li></ul></div><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>Now just annotate your test class with <code class="literal">@AutoConfigureStubRunner</code>. In the annotation, provide
|
||||
the group-id and artifact-id for <code class="literal">Spring Cloud Contract Stub Runner</code> to run the collaborators' stubs for you.</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>Use the <code class="literal">REMOTE</code> stubsMode when downloading stubs from an online repository and <code class="literal">LOCAL</code> for offline work.</p></td></tr></table></div><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>
|
||||
<em><span class="hl-annotation" style="color: gray">@DirtiesContext</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 in your integration test, you will be able to 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="_a_three_minute_tour" href="#_a_three_minute_tour"></a>2.3.2 A three minute tour</h3></div></div></div><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="_on_the_producer_side_2" href="#_on_the_producer_side_2"></a>On the Producer Side</h4></div></div></div><p>In order to start working with <code class="literal">Spring Cloud Contract</code>, add files with REST/ messaging contracts expressed in either
|
||||
Groovy DSL or YAML to the contracts directory set by the
|
||||
<code class="literal">contractsDslDir</code> property, by default <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, etc.). A sample HTTP stub contract in Groovy DSL would look like this:</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 <span class="hl-number">200</span>
|
||||
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>While the same contract expressed in YAML would look the following way:</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, the input and the output messages can be defined (taking into account from and
|
||||
where to it was sent, the message body and header), as well as the methods that should be called after the message
|
||||
is received or the methods that, when called, should trigger a message.
|
||||
An example of a Camel messaging contract expressed in Groovy DSL whould look like this:</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>While, the same contract expressed in YAML would look as in the code below:</p><pre class="programlisting">label: some_label
|
||||
input:
|
||||
messageFrom: jms:delete
|
||||
messageBody:
|
||||
bookName: 'foo'
|
||||
messageHeaders:
|
||||
sample: header
|
||||
assertThat: bookWasDeleted()</pre><p>Then, add Spring Cloud Contract Verifier dependency and plugin to your build file:</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><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>Now, running <code class="literal">./mvnw clean install</code> will cause tests that verify the application
|
||||
compliance with the added contracts to be automatically generated, by default under <code class="literal">org.springframework.cloud.contract.verifier.tests.</code>.</p><p>A sample auto-generated test for an HTTP contract would look the following way:</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 sample above uses Spring’s <code class="literal">MockMvc</code> to run the tests. This is the default test mode for HTTP
|
||||
contracts, however also JAX-RX client and explicit HTTP invocations can be used as well (just change
|
||||
the <code class="literal">testMode</code> property of the plugin to <code class="literal">JAX-RS</code> or <code class="literal">EXPLICIT</code>.</p><p>Apart from the default JUnit, you can also use Spock tests, instead, by setting the plugin <code class="literal">testFramework</code>
|
||||
property to <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>A sample auto-generated test in Spock for a messaging stub contract would look similar to this:</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 will fail.</p><p>To make them pass, the correct implementation of handling either HTTP requests or messages
|
||||
will have to be added. Also, a correct base test class for auto-generated tests needs to be added to the project.
|
||||
This class will be 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 will pass, and both the application
|
||||
and the stub artifacts will be built and installed in the local Maven repository. Information about
|
||||
installing the stubs jar to the local repository will appear in the logs:</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 changes can now be merged and both the application and the stub artifacts may be published 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 will
|
||||
automatically generate tests for HTTP contracts and execute them in <code class="literal">EXPLICIT</code> test mode, then, if
|
||||
the tests pass, generate Wiremock stubs and -optionally- publish them to an artifact manager. In order to use the
|
||||
image, it’s sufficient to 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="_on_the_consumer_side_2" href="#_on_the_consumer_side_2"></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/
|
||||
messaging route that simulates the actual service.</p><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>Get the Producer-side stubs installed in your Maven repository by either:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">checking out the Producer side repository, adding contracts and generating the stubs by running:</li></ul></div><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 would fail;</p></td></tr></table></div><p>or:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">getting already existing producer service stubs from a remote repository; to do this, simply pass the
|
||||
stub artifact ids and artifact repository url as <code class="literal">Spring Cloud Contract Stub Runner</code> properties:</li></ul></div><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>Now just annotate your test class with <code class="literal">@AutoConfigureStubRunner</code>. In the annotation, provide
|
||||
the group-id and artifact-id for <code class="literal">Spring Cloud Contract Stub Runner</code> to run the collaborators' stubs for you.</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>Use the <code class="literal">REMOTE</code> stubsMode when downloading stubs from an online repository and <code class="literal">LOCAL</code> for offline work.</p></td></tr></table></div><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>
|
||||
<em><span class="hl-annotation" style="color: gray">@DirtiesContext</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 in your integration test, you will be able to receive stubbed versions of HTTP responses or messages that are
|
||||
expected to be emitted by the collaborator service. You will see entries similar to theses 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>2.3.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>
|
||||
@@ -138,7 +328,7 @@ response: # (7)
|
||||
#(9) - and JSON body equal to
|
||||
# { "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" }
|
||||
#(10) - with header `Content-Type` equal to `application/json;charset=UTF-8`</pre><p>
|
||||
</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_client_side" href="#_client_side"></a>2.3.2 Client Side</h3></div></div></div><p>Spring Cloud Contract generates stubs, which you can use during client-side testing.
|
||||
</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_client_side" href="#_client_side"></a>2.3.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,
|
||||
@@ -150,7 +340,7 @@ You would like to feed that instance with a proper stub definition.</p><p>At som
|
||||
<em><span class="hl-annotation" style="color: gray">@DirtiesContext</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>2.3.3 Server Side</h3></div></div></div><p>Since you are developing your stub, you need to be sure that it actually resembles your
|
||||
(or random) port.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_server_side" href="#_server_side"></a>2.3.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>
|
||||
@@ -383,7 +573,7 @@ of an identifier or a timestamp, you need not hardcode a value. You want to allo
|
||||
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.
|
||||
<a class="link" href="https://cloud.spring.io/spring-cloud-contract/spring-cloud-contract.html#_contract_dsl" target="_top">Consult the docs
|
||||
<a class="link" href="https://cloud.spring.io/spring-cloud-contract/single/spring-cloud-contract.html#_contract_dsl" target="_top">Consult the docs
|
||||
for more information.</a> 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
|
||||
@@ -441,7 +631,7 @@ Application service</code>):</p><p>Add the <code class="literal">Spring Cloud Co
|
||||
<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.</p><pre class="programlisting"><em><span class="hl-annotation" style="color: gray">@RunWith(SpringRunner.class)</span></em>
|
||||
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>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<html><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<title>5. Spring Cloud Contract Verifier Messaging</title><link rel="stylesheet" type="text/css" href="css/manual-multipage.css"><meta name="generator" content="DocBook XSL Stylesheets V1.78.1"><link rel="home" href="multi_spring-cloud-contract.html" title="Spring Cloud Contract"><link rel="up" href="multi_spring-cloud-contract.html" title="Spring Cloud Contract"><link rel="prev" href="multi__spring_cloud_contract_verifier_setup.html" title="4. Spring Cloud Contract Verifier Setup"><link rel="next" href="multi__spring_cloud_contract_stub_runner.html" title="6. Spring Cloud Contract Stub Runner"></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">5. Spring Cloud Contract Verifier Messaging</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="multi__spring_cloud_contract_verifier_setup.html">Prev</a> </td><th width="60%" align="center"> </th><td width="20%" align="right"> <a accesskey="n" href="multi__spring_cloud_contract_stub_runner.html">Next</a></td></tr></table><hr></div><div class="chapter"><div class="titlepage"><div><div><h1 class="title"><a name="_spring_cloud_contract_verifier_messaging" href="#_spring_cloud_contract_verifier_messaging"></a>5. Spring Cloud Contract Verifier Messaging</h1></div></div></div><p>Spring Cloud Contract Verifier lets you verify applications that uses messaging as a
|
||||
<title>5. Spring Cloud Contract Verifier Messaging</title><link rel="stylesheet" type="text/css" href="css/manual-multipage.css"><meta name="generator" content="DocBook XSL Stylesheets V1.78.1"><link rel="home" href="multi_spring-cloud-contract.html" title="Spring Cloud Contract"><link rel="up" href="multi_spring-cloud-contract.html" title="Spring Cloud Contract"><link rel="prev" href="multi__spring_cloud_contract_verifier_setup.html" title="4. Spring Cloud Contract Verifier Setup"><link rel="next" href="multi__spring_cloud_contract_stub_runner.html" title="6. Spring Cloud Contract Stub Runner"></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">5. Spring Cloud Contract Verifier Messaging</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="multi__spring_cloud_contract_verifier_setup.html">Prev</a> </td><th width="60%" align="center"> </th><td width="20%" align="right"> <a accesskey="n" href="multi__spring_cloud_contract_stub_runner.html">Next</a></td></tr></table><hr></div><div class="chapter"><div class="titlepage"><div><div><h1 class="title"><a name="_spring_cloud_contract_verifier_messaging" href="#_spring_cloud_contract_verifier_messaging"></a>5. Spring Cloud Contract Verifier Messaging</h1></div></div></div><p>Spring Cloud Contract Verifier lets you verify applications that use messaging as a
|
||||
means of communication. All of the integrations shown in this document work with Spring,
|
||||
but you can also create one of your own and use that.</p><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_integrations" href="#_integrations"></a>5.1 Integrations</h2></div></div></div><p>You can use one of the following four integration configurations:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">Apache Camel</li><li class="listitem">Spring Integration</li><li class="listitem">Spring Cloud Stream</li><li class="listitem">Spring AMQP</li></ul></div><p>Since we use Spring Boot, if you have added one of these libraries to the classpath, all
|
||||
the messaging configuration is automatically set up.</p><div class="important" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Important"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Important]" src="images/important.png"></td><th align="left">Important</th></tr><tr><td align="left" valign="top"><p>Remember to put <code class="literal">@AutoConfigureMessageVerifier</code> on the base class of your
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -168,6 +168,305 @@ used to test contracts between applications and not to simulate full behavior.</
|
||||
<section xml:id="_how_it_works">
|
||||
<title>How It Works</title>
|
||||
<simpara>This section explores how Spring Cloud Contract Verifier with Stub Runner works.</simpara>
|
||||
<section xml:id="_a_three_second_tour">
|
||||
<title>A three second tour</title>
|
||||
<section xml:id="_on_the_producer_side">
|
||||
<title>On the Producer Side</title>
|
||||
<simpara>In order to start working with <literal>Spring Cloud Contract</literal>, add files with REST/ messaging contracts expressed in either
|
||||
Groovy DSL or YAML to the contracts directory set by the
|
||||
<literal>contractsDslDir</literal> property, by default <literal>$rootDir/src/test/resources/contracts</literal>.</simpara>
|
||||
<simpara>Then, add Spring Cloud Contract Verifier dependency and plugin to your build file:</simpara>
|
||||
<programlisting language="xml" linenumbering="unnumbered"><dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-contract-verifier</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency></programlisting>
|
||||
<programlisting language="xml" linenumbering="unnumbered"><plugin>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
|
||||
<version>${spring-cloud-contract.version}</version>
|
||||
<extensions>true</extensions>
|
||||
</plugin></programlisting>
|
||||
<simpara>Now, running <literal>./mvnw clean install</literal> will cause tests that verify the application
|
||||
compliance with the added contracts to be automatically generated, by default under <literal>org.springframework.cloud.contract.verifier.tests.</literal>.</simpara>
|
||||
<simpara>As the implementation of the functionalities described by the contracts is not yet present,
|
||||
the tests will fail.</simpara>
|
||||
<simpara>To make them pass, the correct implementation of either handling HTTP requests or messages
|
||||
will have to be added. Also, a correct base test class for auto-generated tests needs to be added to the project.
|
||||
This class will be extended by all the auto-generated tests and it should contain all the setup
|
||||
necessary to run them (for example <literal>RestAssuredMockMvc</literal> controller setup or messaging test setup).</simpara>
|
||||
<simpara>Once the implementation and the test base class are in place, the tests will pass, and both the application
|
||||
and the stub artifacts will be 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.</simpara>
|
||||
</section>
|
||||
<section xml:id="_on_the_consumer_side">
|
||||
<title>On the Consumer Side</title>
|
||||
<simpara><literal>Spring Cloud Contract Stub Runner</literal> can be used in the integration tests to get a running WireMock instance/
|
||||
messaging route that simulates the actual service.</simpara>
|
||||
<simpara>Add the dependency to <literal>Spring Cloud Contract Stub Runner</literal>:</simpara>
|
||||
<programlisting language="xml" linenumbering="unnumbered"><dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency></programlisting>
|
||||
<simpara>Get the Producer-side stubs installed in your Maven repository by either:</simpara>
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<simpara>checking out the Producer side repository, adding contracts and generating the stubs by running:</simpara>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
<programlisting language="bash" linenumbering="unnumbered">$ cd local-http-server-repo
|
||||
$ ./mvnw clean install -DskipTests</programlisting>
|
||||
<tip>
|
||||
<simpara>The tests are being skipped because the Producer-side contract implementation is not in place yet,
|
||||
so the automatically-generated contract tests would fail;</simpara>
|
||||
</tip>
|
||||
<simpara>or:</simpara>
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<simpara>getting already existing producer service stubs from a remote repository; to do this, simply pass the
|
||||
stub artifact ids and artifact repository url as <literal>Spring Cloud Contract Stub Runner</literal> properties:</simpara>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
<programlisting language="yaml" linenumbering="unnumbered">stubrunner:
|
||||
ids: 'com.example:http-server-dsl:+:stubs:8080'
|
||||
repositoryRoot: http://repo.spring.io/libs-snapshot</programlisting>
|
||||
<simpara>Now just annotate your test class with <literal>@AutoConfigureStubRunner</literal>. In the annotation, provide
|
||||
the group-id and artifact-id for <literal>Spring Cloud Contract Stub Runner</literal> to run the collaborators' stubs for you.</simpara>
|
||||
<tip>
|
||||
<simpara>Use the <literal>REMOTE</literal> stubsMode when downloading stubs from an online repository and <literal>LOCAL</literal> for offline work.</simpara>
|
||||
</tip>
|
||||
<programlisting language="java" linenumbering="unnumbered">@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(webEnvironment=WebEnvironment.NONE)
|
||||
@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:6565"},
|
||||
stubsMode = StubRunnerProperties.StubsMode.LOCAL)
|
||||
@DirtiesContext
|
||||
public class LoanApplicationServiceTests {</programlisting>
|
||||
<simpara>Now in your integration test, you will be able to receive stubbed versions of HTTP responses or messages that are
|
||||
expected to be emitted by the collaborator service.</simpara>
|
||||
</section>
|
||||
</section>
|
||||
<section xml:id="_a_three_minute_tour">
|
||||
<title>A three minute tour</title>
|
||||
<section xml:id="_on_the_producer_side_2">
|
||||
<title>On the Producer Side</title>
|
||||
<simpara>In order to start working with <literal>Spring Cloud Contract</literal>, add files with REST/ messaging contracts expressed in either
|
||||
Groovy DSL or YAML to the contracts directory set by the
|
||||
<literal>contractsDslDir</literal> property, by default <literal>$rootDir/src/test/resources/contracts</literal>.</simpara>
|
||||
<simpara>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, etc.). A sample HTTP stub contract in Groovy DSL would look like this:</simpara>
|
||||
<programlisting language="groovy" linenumbering="unnumbered">package contracts
|
||||
|
||||
org.springframework.cloud.contract.spec.Contract.make {
|
||||
request {
|
||||
method 'PUT'
|
||||
url '/fraudcheck'
|
||||
body([
|
||||
"client.id": $(regex('[0-9]{10}')),
|
||||
loanAmount: 99999
|
||||
])
|
||||
headers {
|
||||
contentType('application/json')
|
||||
}
|
||||
}
|
||||
response {
|
||||
status 200
|
||||
body([
|
||||
fraudCheckStatus: "FRAUD",
|
||||
"rejection.reason": "Amount too high"
|
||||
])
|
||||
headers {
|
||||
contentType('application/json')
|
||||
}
|
||||
}
|
||||
}</programlisting>
|
||||
<simpara>While the same contract expressed in YAML would look the following way:</simpara>
|
||||
<programlisting language="yaml" linenumbering="unnumbered">request:
|
||||
method: PUT
|
||||
url: /fraudcheck
|
||||
body:
|
||||
"client.id": 1234567890
|
||||
loanAmount: 99999
|
||||
headers:
|
||||
Content-Type: application/json
|
||||
matchers:
|
||||
body:
|
||||
- path: $.['client.id']
|
||||
type: by_regex
|
||||
value: "[0-9]{10}"
|
||||
response:
|
||||
status: 200
|
||||
body:
|
||||
fraudCheckStatus: "FRAUD"
|
||||
"rejection.reason": "Amount too high"
|
||||
headers:
|
||||
Content-Type: application/json;charset=UTF-8</programlisting>
|
||||
<simpara>In the case of messaging, the input and the output messages can be defined (taking into account from and
|
||||
where to it was sent, the message body and header), as well as the methods that should be called after the message
|
||||
is received or the methods that, when called, should trigger a message.
|
||||
An example of a Camel messaging contract expressed in Groovy DSL whould look like this:</simpara>
|
||||
<programlisting language="groovy" linenumbering="unnumbered">def contractDsl = Contract.make {
|
||||
label 'some_label'
|
||||
input {
|
||||
messageFrom('jms:delete')
|
||||
messageBody([
|
||||
bookName: 'foo'
|
||||
])
|
||||
messageHeaders {
|
||||
header('sample', 'header')
|
||||
}
|
||||
assertThat('bookWasDeleted()')
|
||||
}
|
||||
}</programlisting>
|
||||
<simpara>While, the same contract expressed in YAML would look as in the code below:</simpara>
|
||||
<programlisting language="yml" linenumbering="unnumbered">label: some_label
|
||||
input:
|
||||
messageFrom: jms:delete
|
||||
messageBody:
|
||||
bookName: 'foo'
|
||||
messageHeaders:
|
||||
sample: header
|
||||
assertThat: bookWasDeleted()</programlisting>
|
||||
<simpara>Then, add Spring Cloud Contract Verifier dependency and plugin to your build file:</simpara>
|
||||
<programlisting language="xml" linenumbering="unnumbered"><dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-contract-verifier</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency></programlisting>
|
||||
<programlisting language="xml" linenumbering="unnumbered"><plugin>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
|
||||
<version>${spring-cloud-contract.version}</version>
|
||||
<extensions>true</extensions>
|
||||
</plugin></programlisting>
|
||||
<simpara>Now, running <literal>./mvnw clean install</literal> will cause tests that verify the application
|
||||
compliance with the added contracts to be automatically generated, by default under <literal>org.springframework.cloud.contract.verifier.tests.</literal>.</simpara>
|
||||
<simpara>A sample auto-generated test for an HTTP contract would look the following way:</simpara>
|
||||
<programlisting language="java" linenumbering="unnumbered">@Test
|
||||
public void validate_shouldMarkClientAsFraud() throws Exception {
|
||||
// given:
|
||||
MockMvcRequestSpecification request = given()
|
||||
.header("Content-Type", "application/vnd.fraud.v1+json")
|
||||
.body("{\"client.id\":\"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("['rejection.reason']").isEqualTo("Amount too high");
|
||||
}</programlisting>
|
||||
<simpara>The sample above uses Spring’s <literal>MockMvc</literal> to run the tests. This is the default test mode for HTTP
|
||||
contracts, however also JAX-RX client and explicit HTTP invocations can be used as well (just change
|
||||
the <literal>testMode</literal> property of the plugin to <literal>JAX-RS</literal> or <literal>EXPLICIT</literal>.</simpara>
|
||||
<simpara>Apart from the default JUnit, you can also use Spock tests, instead, by setting the plugin <literal>testFramework</literal>
|
||||
property to <literal>Spock</literal>.</simpara>
|
||||
<tip>
|
||||
<simpara>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.</simpara>
|
||||
</tip>
|
||||
<simpara>A sample auto-generated test in Spock for a messaging stub contract would look similar to this:</simpara>
|
||||
<literallayout class="monospaced">[source,groovy,indent=0]</literallayout>
|
||||
<screen>given:
|
||||
ContractVerifierMessage inputMessage = contractVerifierMessaging.create(
|
||||
\'\'\'{"bookName":"foo"}\'\'\',
|
||||
['sample': 'header']
|
||||
)
|
||||
|
||||
when:
|
||||
contractVerifierMessaging.send(inputMessage, 'jms:delete')
|
||||
|
||||
then:
|
||||
noExceptionThrown()
|
||||
bookWasDeleted()</screen>
|
||||
<simpara>As the implementation of the functionalities described by the contracts is not yet present,
|
||||
the tests will fail.</simpara>
|
||||
<simpara>To make them pass, the correct implementation of handling either HTTP requests or messages
|
||||
will have to be added. Also, a correct base test class for auto-generated tests needs to be added to the project.
|
||||
This class will be extended by all the auto-generated tests and it should contain all the setup
|
||||
necessary to run them (for example <literal>RestAssuredMockMvc</literal> controller setup or messaging test setup).</simpara>
|
||||
<simpara>Once the implementation and the test base class are in place, the tests will pass, and both the application
|
||||
and the stub artifacts will be built and installed in the local Maven repository. Information about
|
||||
installing the stubs jar to the local repository will appear in the logs:</simpara>
|
||||
<programlisting language="bash" linenumbering="unnumbered">[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.5.5.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</programlisting>
|
||||
<simpara>The changes can now be merged and both the application and the stub artifacts may be published in an online repository.</simpara>
|
||||
<simpara><emphasis role="strong">Docker Project</emphasis></simpara>
|
||||
<simpara>In order to enable working with contracts while creating applications in non-JVM technologies,
|
||||
the <literal>springcloud/spring-cloud-contract</literal> Docker image has been created. It contains a project that will
|
||||
automatically generate tests for HTTP contracts and execute them in <literal>EXPLICIT</literal> test mode, then, if
|
||||
the tests pass, generate Wiremock stubs and -optionally- publish them to an artifact manager. In order to use the
|
||||
image, it’s sufficient to mount the contracts into the <literal>/contracts</literal> directory and set a few environment variables.</simpara>
|
||||
</section>
|
||||
<section xml:id="_on_the_consumer_side_2">
|
||||
<title>On the Consumer Side</title>
|
||||
<simpara><literal>Spring Cloud Contract Stub Runner</literal> can be used in the integration tests to get a running WireMock instance/
|
||||
messaging route that simulates the actual service.</simpara>
|
||||
<simpara>Add the dependency to <literal>Spring Cloud Contract Stub Runner</literal>:</simpara>
|
||||
<programlisting language="xml" linenumbering="unnumbered"><dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency></programlisting>
|
||||
<simpara>Get the Producer-side stubs installed in your Maven repository by either:</simpara>
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<simpara>checking out the Producer side repository, adding contracts and generating the stubs by running:</simpara>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
<programlisting language="bash" linenumbering="unnumbered">$ cd local-http-server-repo
|
||||
$ ./mvnw clean install -DskipTests</programlisting>
|
||||
<tip>
|
||||
<simpara>The tests are being skipped because the Producer-side contract implementation is not in place yet,
|
||||
so the automatically-generated contract tests would fail;</simpara>
|
||||
</tip>
|
||||
<simpara>or:</simpara>
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<simpara>getting already existing producer service stubs from a remote repository; to do this, simply pass the
|
||||
stub artifact ids and artifact repository url as <literal>Spring Cloud Contract Stub Runner</literal> properties:</simpara>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
<programlisting language="yaml" linenumbering="unnumbered">stubrunner:
|
||||
ids: 'com.example:http-server-dsl:+:stubs:8080'
|
||||
repositoryRoot: http://repo.spring.io/libs-snapshot</programlisting>
|
||||
<simpara>Now just annotate your test class with <literal>@AutoConfigureStubRunner</literal>. In the annotation, provide
|
||||
the group-id and artifact-id for <literal>Spring Cloud Contract Stub Runner</literal> to run the collaborators' stubs for you.</simpara>
|
||||
<tip>
|
||||
<simpara>Use the <literal>REMOTE</literal> stubsMode when downloading stubs from an online repository and <literal>LOCAL</literal> for offline work.</simpara>
|
||||
</tip>
|
||||
<programlisting language="java" linenumbering="unnumbered">@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(webEnvironment=WebEnvironment.NONE)
|
||||
@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:6565"},
|
||||
stubsMode = StubRunnerProperties.StubsMode.LOCAL)
|
||||
@DirtiesContext
|
||||
public class LoanApplicationServiceTests {</programlisting>
|
||||
<simpara>Now in your integration test, you will be able to receive stubbed versions of HTTP responses or messages that are
|
||||
expected to be emitted by the collaborator service. You will see entries similar to theses in the build logs:</simpara>
|
||||
<programlisting language="bash" linenumbering="unnumbered">2016-07-19 14:22:25.403 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Desired version is + - will try to resolve the latest version
|
||||
2016-07-19 14:22:25.438 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Resolved version is 0.0.1-SNAPSHOT
|
||||
2016-07-19 14:22:25.439 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Resolving artifact com.example:http-server:jar:stubs:0.0.1-SNAPSHOT using remote repositories []
|
||||
2016-07-19 14:22:25.451 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Resolved artifact com.example:http-server:jar:stubs:0.0.1-SNAPSHOT to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar
|
||||
2016-07-19 14:22:25.465 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Unpacking stub from JAR [URI: file:/path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar]
|
||||
2016-07-19 14:22:25.475 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Unpacked file to [/var/folders/0p/xwq47sq106x1_g3dtv6qfm940000gq/T/contracts100276532569594265]
|
||||
2016-07-19 14:22:27.737 INFO 41050 --- [ main] o.s.c.c.stubrunner.StubRunnerExecutor : All stubs are now running RunningStubs [namesAndPorts={com.example:http-server:0.0.1-SNAPSHOT:stubs=8080}]</programlisting>
|
||||
</section>
|
||||
</section>
|
||||
<section xml:id="_defining_the_contract">
|
||||
<title>Defining the contract</title>
|
||||
<simpara>As consumers of services, we need to define what exactly we want to achieve. We need to
|
||||
@@ -644,7 +943,7 @@ of an identifier or a timestamp, you need not hardcode a value. You want to allo
|
||||
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.
|
||||
<link xl:href="https://cloud.spring.io/spring-cloud-contract/spring-cloud-contract.html#_contract_dsl">Consult the docs
|
||||
<link xl:href="https://cloud.spring.io/spring-cloud-contract/single/spring-cloud-contract.html#_contract_dsl">Consult the docs
|
||||
for more information.</link> We highly recommend using the map notation!</simpara>
|
||||
<tip>
|
||||
<simpara>You must understand the map notation in order to set up contracts. Please read the
|
||||
@@ -765,7 +1064,7 @@ Application service</literal>):</simpara>
|
||||
<simpara>Annotate your test class with <literal>@AutoConfigureStubRunner</literal>. In the annotation, provide the
|
||||
<literal>group-id</literal> and <literal>artifact-id</literal> 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.</simpara>
|
||||
can also provide the offline work switch (<literal>StubRunnerProperties.StubsMode.LOCAL</literal>).</simpara>
|
||||
<programlisting language="groovy" linenumbering="unnumbered">@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(webEnvironment=WebEnvironment.NONE)
|
||||
@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:6565"},
|
||||
@@ -1067,7 +1366,7 @@ value(client(...), server(...))</programlisting>
|
||||
<programlisting language="groovy" linenumbering="unnumbered">$(consumer(...), producer(...))
|
||||
$(stub(...), test(...))
|
||||
$(client(...), server(...))</programlisting>
|
||||
<simpara>You can read more about this in the <link xl:href="https://cloud.spring.io/spring-cloud-contract/spring-cloud-contract.html#_contract_dsl">Contract DSL section</link>.</simpara>
|
||||
<simpara>You can read more about this in the <link xl:href="https://cloud.spring.io/spring-cloud-contract/single/spring-cloud-contract.html#_contract_dsl">Contract DSL section</link>.</simpara>
|
||||
<simpara>Calling <literal>value()</literal> or <literal>$()</literal> tells Spring Cloud Contract that you will be passing a dynamic value.
|
||||
Inside the <literal>consumer()</literal> method you pass the value that should be used on the consumer side (in the generated stub).
|
||||
Inside the <literal>producer()</literal> method you pass the value that should be used on the producer side (in the generated test).</simpara>
|
||||
@@ -2798,7 +3097,7 @@ The stubs will be here <link xl:href="http://localhost:8081/artifactory/libs-rel
|
||||
</chapter>
|
||||
<chapter xml:id="_spring_cloud_contract_verifier_messaging">
|
||||
<title>Spring Cloud Contract Verifier Messaging</title>
|
||||
<simpara>Spring Cloud Contract Verifier lets you verify applications that uses messaging as a
|
||||
<simpara>Spring Cloud Contract Verifier lets you verify applications that use messaging as a
|
||||
means of communication. All of the integrations shown in this document work with Spring,
|
||||
but you can also create one of your own and use that.</simpara>
|
||||
<section xml:id="_integrations">
|
||||
|
||||
Reference in New Issue
Block a user