From 31b6a775577430022cf1580c4c747ac12de78c4f Mon Sep 17 00:00:00 2001
From: buildmaster
client2 and client3). Expectations are the standard Groovy DSL
contract files as described throughout this documentation. This repository has to produce a JAR file that maps
one to one to the contents of the repo.Example of a pom.xml inside the server folder.
<?xml version="1.0" encoding="UTF-8"?> -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> +<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://maven.apache.org/POM/4.0.0" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> @@ -116,14 +117,16 @@ one to one to the contents of the repo.Example of a
<groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.2.RELEASE</version> - <relativePath /> + <relativePath/> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> - <spring-cloud-contract.version>2.1.1.BUILD-SNAPSHOT</spring-cloud-contract.version> - <spring-cloud-release.version>Greenwich.BUILD-SNAPSHOT</spring-cloud-release.version> + <spring-cloud-contract.version>2.1.1.BUILD-SNAPSHOT + </spring-cloud-contract.version> + <spring-cloud-release.version>Greenwich.BUILD-SNAPSHOT + </spring-cloud-release.version> <excludeBuildFolders>true</excludeBuildFolders> </properties> @@ -210,7 +213,8 @@ one to one to the contents of the repo.Example of a
</project>
As you can see there are no dependencies other than the Spring Cloud Contract Maven Plugin.
Those poms are necessary for the consumer side to run mvn clean install -DskipTests to locally install
stubs of the producer project.
The pom.xml in the root folder can look like this:
<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
@@ -219,7 +223,9 @@ Those poms are necessary for the consumer side to run mvn
<version>0.0.1-SNAPSHOT</version>
<name>Contracts</name>
- <description>Contains all the Spring Cloud Contracts, well, contracts. JAR used by the producers to generate tests and stubs</description>
+ <description>Contains all the Spring Cloud Contracts, well, contracts. JAR used by the
+ producers to generate tests and stubs
+ </description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -249,8 +255,8 @@ Those poms are necessary for the consumer side to run mvn
</plugins>
</build>
-</project>It’s using the assembly plugin in order to build the JAR with all the contracts. Example of such setup is here:
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" +</project>
It’s using the assembly plugin in order to build the JAR with all the contracts. Example of such setup is here:
<assembly xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3" xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd"> <id>project</id> <formats> @@ -281,7 +287,9 @@ of the JAR containing the contracts:<artifactId>spring-cloud-contract-maven-plugin</artifactId> <configuration> <contractsMode>REMOTE</contractsMode> - <contractsRepositoryUrl>http://link/to/your/nexus/or/artifactory/or/sth</contractsRepositoryUrl> + <contractsRepositoryUrl> + http://link/to/your/nexus/or/artifactory/or/sth + </contractsRepositoryUrl> <contractDependency> <groupId>com.example.standalone</groupId> <artifactId>contracts</artifactId> diff --git a/multi/multi__spring_cloud_contract_stub_runner.html b/multi/multi__spring_cloud_contract_stub_runner.html index 9cbf282af8..25b5631645 100644 --- a/multi/multi__spring_cloud_contract_stub_runner.html +++ b/multi/multi__spring_cloud_contract_stub_runner.html @@ -246,7 +246,7 @@ case might be starting WireMock for the given artifact id, on an HTTPs port. Exa } }
You can then reuse it via the annotation
@AutoConfigureStubRunner(mappingsOutputFolder = "target/outputmappings/", - httpServerStubConfigurer = HttpsForFraudDetection)Whenever an https port is found, it will take precedence over the http one.
You can set the following options to the main class:
-c, --classifier Suffix for the jar containing stubs (e. g. 'stubs' if the stub jar would have a 'stubs' classifier for stubs: foobar-stubs ). Defaults to 'stubs' @@ -309,11 +309,20 @@ mappings available for the given server:["uuid" : "f9152eb9-bf77-4c38-8289-90be7d10d0d7" }, ... -]
Stub Runner comes with a JUnit rule thanks to which you can very easily download and run stubs for given group and artifact id:
@ClassRule public static StubRunnerRule rule = new StubRunnerRule() - .repoRoot(repoRoot()) +]
Depending on the provided Stub Runner dependency and the DSL the messaging routes are automatically set up.
Stub Runner comes with a JUnit rule thanks to which you can very easily download and run stubs for given group and artifact id:
@ClassRule +public static StubRunnerRule rule = new StubRunnerRule().repoRoot(repoRoot()) .stubsMode(StubRunnerProperties.StubsMode.REMOTE) - .downloadStub("org.springframework.cloud.contract.verifier.stubs", "loanIssuance") - .downloadStub("org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer");
There’s also a StubRunnerExtension available for JUnit 5. StubRunnerRule and StubRunnerExtension work in a very
+ .downloadStub("org.springframework.cloud.contract.verifier.stubs",
+ "loanIssuance")
+ .downloadStub(
+ "org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer");
+
+@BeforeClass
+@AfterClass
+public static void setupProps() {
+ System.clearProperty("stubrunner.repository.root");
+ System.clearProperty("stubrunner.classifier");
+}
There’s also a StubRunnerExtension available for JUnit 5. StubRunnerRule and StubRunnerExtension work in a very
similar fashion. After the rule/ extension is executed, Stub Runner connects to your Maven repository and for the given list of dependencies tries to:
MessageVerifier interface)Stub Runner uses Eclipse Aether mechanism to download the Maven dependencies. Check their docs for more information.
Since the StubRunnerRule and StubRunnerExtension implement the StubFinder they allow you to find the started stubs:
package org.springframework.cloud.contract.stubrunner; @@ -323,6 +332,11 @@ Check their import org.springframework.cloud.contract.spec.Contract; +/** + * Contract for finding registered stubs. + * + * @author Marcin Grzejszczak + */ public interface StubFinder extends StubTrigger { /** @@ -330,7 +344,9 @@ Check their URL findStubUrl(String groupId, String artifactId) throws StubNotFoundException; @@ -340,20 +356,23 @@ Check their URL findStubUrl(String ivyNotation) throws StubNotFoundException; /** - * Returns all running stubs + * @return all running stubs */ RunningStubs findAllRunningStubs(); /** - * Returns the list of Contracts + * @return the list of Contracts */ Map<StubConfiguration, Collection<Contract>> getContracts(); -}
Example of usage in Spock tests:
@ClassRule @Shared StubRunnerRule rule = new StubRunnerRule() +}
Example of usage in Spock tests:
@ClassRule +@Shared +StubRunnerRule rule = new StubRunnerRule() .stubsMode(StubRunnerProperties.StubsMode.REMOTE) .repoRoot(StubRunnerRuleSpec.getResource("/m2repo/repository").toURI().toString()) .downloadStub("org.springframework.cloud.contract.verifier.stubs", "loanIssuance") @@ -381,64 +400,104 @@ def 'should outp def url = rule.findStubUrl('fraudDetectionServer') then: new File("target/outputmappingsforrule", "fraudDetectionServer_${url.port}").exists() -}
Example of usage in JUnit tests:
@Test -public void should_start_wiremock_servers() throws Exception { - // expect: 'WireMocks are running' - then(rule.findStubUrl("org.springframework.cloud.contract.verifier.stubs", "loanIssuance")).isNotNull(); +}
Example of usage in JUnit tests:
@Test + public void should_start_wiremock_servers() throws Exception { + // expect: 'WireMocks are running' + then(rule.findStubUrl("org.springframework.cloud.contract.verifier.stubs", + "loanIssuance")).isNotNull(); then(rule.findStubUrl("loanIssuance")).isNotNull(); - then(rule.findStubUrl("loanIssuance")).isEqualTo(rule.findStubUrl("org.springframework.cloud.contract.verifier.stubs", "loanIssuance")); - then(rule.findStubUrl("org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer")).isNotNull(); - // and: + then(rule.findStubUrl("loanIssuance")).isEqualTo(rule.findStubUrl( + "org.springframework.cloud.contract.verifier.stubs", "loanIssuance")); + then(rule.findStubUrl( + "org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer")) + .isNotNull(); + // and: then(rule.findAllRunningStubs().isPresent("loanIssuance")).isTrue(); - then(rule.findAllRunningStubs().isPresent("org.springframework.cloud.contract.verifier.stubs", "fraudDetectionServer")).isTrue(); - then(rule.findAllRunningStubs().isPresent("org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer")).isTrue(); - // and: 'Stubs were registered' - then(httpGet(rule.findStubUrl("loanIssuance").toString() + "/name")).isEqualTo("loanIssuance"); - then(httpGet(rule.findStubUrl("fraudDetectionServer").toString() + "/name")).isEqualTo("fraudDetectionServer"); + then(rule.findAllRunningStubs().isPresent( + "org.springframework.cloud.contract.verifier.stubs", + "fraudDetectionServer")).isTrue(); + then(rule.findAllRunningStubs().isPresent( + "org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer")) + .isTrue(); + // and: 'Stubs were registered' + then(httpGet(rule.findStubUrl("loanIssuance").toString() + "/name")) + .isEqualTo("loanIssuance"); + then(httpGet(rule.findStubUrl("fraudDetectionServer").toString() + "/name")) + .isEqualTo("fraudDetectionServer"); + } + + private String httpGet(String url) throws Exception { + try (InputStream stream = URI.create(url).toURL().openStream()) { + return StreamUtils.copyToString(stream, Charset.forName("UTF-8")); + } + } + }
JUnit 5 Extension example:
// Visible for Junit @RegisterExtension static StubRunnerExtension stubRunnerExtension = new StubRunnerExtension() - .repoRoot(repoRoot()) - .stubsMode(StubRunnerProperties.StubsMode.REMOTE) - .downloadStub("org.springframework.cloud.contract.verifier.stubs", "loanIssuance") - .downloadStub("org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer") - .withMappingsOutputFolder("target/outputmappingsforrule"); + .repoRoot(repoRoot()).stubsMode(StubRunnerProperties.StubsMode.REMOTE) + .downloadStub("org.springframework.cloud.contract.verifier.stubs", + "loanIssuance") + .downloadStub( + "org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer") + .withMappingsOutputFolder("target/outputmappingsforrule"); -@Test -void should_start_WireMock_servers() { - assertThat(stubRunnerExtension.findStubUrl("org.springframework.cloud.contract.verifier.stubs", - "loanIssuance")).isNotNull(); - assertThat(stubRunnerExtension.findStubUrl("loanIssuance")).isNotNull(); - assertThat(stubRunnerExtension.findStubUrl("loanIssuance")).isEqualTo(stubRunnerExtension - .findStubUrl("org.springframework.cloud.contract.verifier.stubs", "loanIssuance")); - assertThat(stubRunnerExtension - .findStubUrl("org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer")).isNotNull(); +@BeforeAll +@AfterAll +static void setupProps() { + System.clearProperty("stubrunner.repository.root"); + System.clearProperty("stubrunner.classifier"); +} + +private static String repoRoot() { + try { + return StubRunnerRuleJUnitTest.class.getResource("/m2repo/repository/") + .toURI().toString(); + } + catch (Exception e) { + return ""; + } }
Check the Common properties for JUnit and Spring for more information on how to apply global configuration of Stub Runner.
![]() | Important |
|---|---|
To use the JUnit rule or JUnit 5 extension together with messaging, you have to provide an implementation of the
|
The stub downloader honors Maven settings for a different local repository folder. Authentication details for repositories and profiles are currently not taken into account, so you need to specify it using the properties mentioned above.
You can also run your stubs on fixed ports. You can do it in two different ways. One is to pass it in the properties, and the other via fluent API of -JUnit rule.
When using the StubRunnerRule or StubRunnerExtension you can add a stub to download and then pass the port for the last downloaded stub.
@ClassRule public static StubRunnerRule rule = new StubRunnerRule() - .repoRoot(repoRoot()) +JUnit rule.
When using the StubRunnerRule or StubRunnerExtension you can add a stub to download and then pass the port for the last downloaded stub.
@ClassRule +public static StubRunnerRule rule = new StubRunnerRule().repoRoot(repoRoot()) .stubsMode(StubRunnerProperties.StubsMode.REMOTE) - .downloadStub("org.springframework.cloud.contract.verifier.stubs", "loanIssuance") - .withPort(12345) - .downloadStub("org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer:12346");
You can see that for this example the following test is valid:
then(rule.findStubUrl("loanIssuance")).isEqualTo(URI.create("http://localhost:12345").toURL()); -then(rule.findStubUrl("fraudDetectionServer")).isEqualTo(URI.create("http://localhost:12346").toURL());
Sets up Spring configuration of the Stub Runner project.
By providing a list of stubs inside your configuration file the Stub Runner automatically downloads + .downloadStub("org.springframework.cloud.contract.verifier.stubs", + "loanIssuance") + .withPort(12345).downloadStub( + "org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer:12346"); + +@BeforeClass +@AfterClass +public static void setupProps() { + System.clearProperty("stubrunner.repository.root"); + System.clearProperty("stubrunner.classifier"); +}
You can see that for this example the following test is valid:
then(rule.findStubUrl("loanIssuance")) + .isEqualTo(URI.create("http://localhost:12345").toURL()); +then(rule.findStubUrl("fraudDetectionServer")) + .isEqualTo(URI.create("http://localhost:12346").toURL());
Sets up Spring configuration of the Stub Runner project.
By providing a list of stubs inside your configuration file the Stub Runner automatically downloads and registers in WireMock the selected stubs.
If you want to find the URL of your stubbed dependency you can autowire the StubFinder interface and use
its methods as presented below:
@ContextConfiguration(classes = Config, loader = SpringBootContextLoader) @SpringBootTest(properties = [" stubrunner.cloud.enabled=false", 'foo=${stubrunner.runningstubs.fraudDetectionServer.port}', 'fooWithGroup=${stubrunner.runningstubs.org.springframework.cloud.contract.verifier.stubs.fraudDetectionServer.port}']) @AutoConfigureStubRunner(mappingsOutputFolder = "target/outputmappings/", - httpServerStubConfigurer = HttpsForFraudDetection) + httpServerStubConfigurer = HttpsForFraudDetection) @ActiveProfiles("test") class StubRunnerConfigurationSpec extends Specification { - @Autowired StubFinder stubFinder - @Autowired Environment environment - @StubRunnerPort("fraudDetectionServer") int fraudDetectionServerPort - @StubRunnerPort("org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer") int fraudDetectionServerPortWithGroupId - @Value('${foo}') Integer foo + @Autowired + StubFinder stubFinder + @Autowired + Environment environment + @StubRunnerPort("fraudDetectionServer") + int fraudDetectionServerPort + @StubRunnerPort("org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer") + int fraudDetectionServerPortWithGroupId + @Value('${foo}') + Integer foo @BeforeClass @AfterClass @@ -536,8 +595,7 @@ its methods as presented below:return httpStubConfiguration } } -} -// end::test[]for the following configuration file:
stubrunner: +}for the following configuration file:
stubrunner: repositoryRoot: classpath:m2repo/repository/ ids: - org.springframework.cloud.contract.verifier.stubs:loanIssuance @@ -546,8 +604,8 @@ its methods as presented below:@AutoConfigureStubRunner. Below you can find an example of achieving the same result by setting values on the annotation.@AutoConfigureStubRunner( ids = ["org.springframework.cloud.contract.verifier.stubs:loanIssuance", - "org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer", - "org.springframework.cloud.contract.verifier.stubs:bootService"], + "org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer", + "org.springframework.cloud.contract.verifier.stubs:bootService"], stubsMode = StubRunnerProperties.StubsMode.REMOTE, repositoryRoot = "classpath:m2repo/repository/")Stub Runner Spring registers environment variables in the following manner for every registered WireMock server. Example for Stub Runner ids @@ -568,8 +626,7 @@ or
DiscoveryClientdirectly, to call those stubbed }for the following configuration file
stubrunner: idsToServiceIds: ivyNotation: someValueInsideYourCode - fraudDetectionServer: someNameThatShouldMapFraudDetectionServer -# end::ids[]In your integration tests you typically don’t want to call neither a discovery service (e.g. Eureka) + fraudDetectionServer: someNameThatShouldMapFraudDetectionServer
In your integration tests you typically don’t want to call neither a discovery service (e.g. Eureka) or Config Server. That’s why you create an additional test configuration in which you want to disable these features.
Due to certain limitations of
spring-cloud-commonsto achieve this you have disable these properties via a static block like presented below (example for Eureka)//Hack to work around https://github.com/spring-cloud/spring-cloud-commons/issues/156 @@ -600,7 +657,8 @@ the Stub Runner server. It will be available at port8750< @ActiveProfiles("test") class StubRunnerBootSpec extends Specification { - @Autowired StubRunning stubRunning + @Autowired + StubRunning stubRunning def setup() { RestAssuredMockMvc.standaloneSetup(new HttpStubsController(stubRunning), @@ -641,7 +699,7 @@ the Stub Runner server. It will be available at port8750< String response = RestAssuredMockMvc.get('/triggers').body.asString() then: def root = new JsonSlurper().parseText(response) - root.'org.springframework.cloud.contract.verifier.stubs:bootService:0.0.1-SNAPSHOT:stubs'?.containsAll(["delete_book","return_book_1","return_book_2"]) + root.'org.springframework.cloud.contract.verifier.stubs:bootService:0.0.1-SNAPSHOT:stubs'?.containsAll(["delete_book", "return_book_1", "return_book_2"]) } def 'should trigger a messaging label'() { @@ -703,15 +761,17 @@ the Stub Runner server. It will be available at port8750< }As you can see we want to start a Stub Runner Boot server
@EnableStubRunnerServer, enable Eureka client@EnableEurekaClientand we want to have the stub runner feature turned on@AutoConfigureStubRunner.Now let’s assume that we want to start this application so that the stubs get automatically registered. We can do it by running the app
java -jar ${SYSTEM_PROPS} stub-runner-boot-eureka-example.jarwhere -${SYSTEM_PROPS}would contain the following list of properties-Dstubrunner.repositoryRoot=http://repo.spring.io/snapshots (1) --Dstubrunner.cloud.stubbed.discovery.enabled=false (2) --Dstubrunner.ids=org.springframework.cloud.contract.verifier.stubs:loanIssuance,org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer,org.springframework.cloud.contract.verifier.stubs:bootService (3) --Dstubrunner.idsToServiceIds.fraudDetectionServer=someNameThatShouldMapFraudDetectionServer (4) - -(1) - we tell Stub Runner where all the stubs reside -(2) - we don't want the default behaviour where the discovery service is stubbed. That's why the stub registration will be picked -(3) - we provide a list of stubs to download -(4) - we provide a list of artifactId to serviceId mappingThat way your deployed application can send requests to started WireMock servers via the service +
${SYSTEM_PROPS}would contain the following list of properties* -Dstubrunner.repositoryRoot=http://repo.spring.io/snapshots (1) +* -Dstubrunner.cloud.stubbed.discovery.enabled=false (2) +* -Dstubrunner.ids=org.springframework.cloud.contract.verifier.stubs:loanIssuance,org. +* springframework.cloud.contract.verifier.stubs:fraudDetectionServer,org.springframework. +* cloud.contract.verifier.stubs:bootService (3) +* -Dstubrunner.idsToServiceIds.fraudDetectionServer= +* someNameThatShouldMapFraudDetectionServer (4) +* +* (1) - we tell Stub Runner where all the stubs reside (2) - we don't want the default +* behaviour where the discovery service is stubbed. That's why the stub registration will +* be picked (3) - we provide a list of stubs to download (4) - we provide a list ofThat way your deployed application can send requests to started WireMock servers via the service discovery. Most likely points 1-3 could be set by default in
application.ymlcause they are not likely to change. That way you can provide only the list of stubs to download whenever you start the Stub Runner Boot.There are cases in which 2 consumers of the same endpoint want to have 2 different responses.
Tip This approach also allows you to immediately know which consumer is using which part of your API. diff --git a/multi/multi__spring_cloud_contract_verifier_introduction.html b/multi/multi__spring_cloud_contract_verifier_introduction.html index 5b366a5a1b..3f64d7c545 100644 --- a/multi/multi__spring_cloud_contract_verifier_introduction.html +++ b/multi/multi__spring_cloud_contract_verifier_introduction.html @@ -120,19 +120,19 @@ org.springframework.cloud.contract.spec.Contract.make { "rejection.reason": "Amount too high" headers: Content-Type: application/json;charset=UTF-8
In the case of messaging, you can define:
- The input and the output messages can be defined (taking into account from and where it -was sent, the message body, and the header).
- The methods that should be called after the message is received.
- The methods that, when called, should trigger a message.
The following example shows a Camel messaging contract expressed in Groovy DSL:
def contractDsl = Contract.make { - label 'some_label' - input { - messageFrom('jms:delete') - messageBody([ - bookName: 'foo' - ]) - messageHeaders { - header('sample', 'header') - } - assertThat('bookWasDeleted()') - } -}The following example shows the same contract expressed in YAML:
label: some_label +was sent, the message body, and the header).The methods that should be called after the message is received. The methods that, when called, should trigger a message. The following example shows a Camel messaging contract expressed in Groovy DSL:
def contractDsl = Contract.make { + label 'some_label' + input { + messageFrom('jms:delete') + messageBody([ + bookName: 'foo' + ]) + messageHeaders { + header('sample', 'header') + } + assertThat('bookWasDeleted()') + } + }The following example shows the same contract expressed in YAML:
label: some_label input: messageFrom: jms:delete messageBody: @@ -262,7 +262,23 @@ to the following in the build logs:package contracts +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package contracts org.springframework.cloud.contract.spec.Contract.make { request { // (1) @@ -270,7 +286,7 @@ org.springframework.cloud.contract.spec.Contract.make { url '/fraudcheck' // (3) body([ // (4) "client.id": $(regex('[0-9]{10}')), - loanAmount: 99999 + loanAmount : 99999 ]) headers { // (5) contentType('application/json') @@ -279,7 +295,7 @@ org.springframework.cloud.contract.spec.Contract.make { response { // (6) status OK() // (7) body([ // (8) - fraudCheckStatus: "FRAUD", + fraudCheckStatus : "FRAUD", "rejection.reason": "Amount too high" ]) headers { // (9) @@ -374,13 +390,12 @@ response: # (7) #(10) - with header `Content-Type` equal to `application/json;charset=UTF-8`
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.
At some point in time, you need to send a request to the Fraud Detection service.
ResponseEntity<FraudServiceResponse> response = - restTemplate.exchange("http://localhost:" + port + "/fraudcheck", HttpMethod.PUT, - new HttpEntity<>(request, httpHeaders), - FraudServiceResponse.class);Annotate your test class with
@AutoConfigureStubRunner. In the annotation provide the group id and artifact id for the Stub Runner to download stubs of your collaborators.@RunWith(SpringRunner.class) +You would like to feed that instance with a proper stub definition.At some point in time, you need to send a request to the Fraud Detection service.
ResponseEntity<FraudServiceResponse> response = restTemplate.exchange( + "http://localhost:" + port + "/fraudcheck", HttpMethod.PUT, + new HttpEntity<>(request, httpHeaders), FraudServiceResponse.class);Annotate your test class with
@AutoConfigureStubRunner. In the annotation provide the group id and artifact id for the Stub Runner to download stubs of your collaborators.@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = WebEnvironment.NONE) -@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:6565"}, - stubsMode = StubRunnerProperties.StubsMode.LOCAL) +@AutoConfigureStubRunner(ids = { + "com.example:http-server-dsl:+:stubs:6565" }, stubsMode = StubRunnerProperties.StubsMode.LOCAL) public class LoanApplicationServiceTests {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.
Since you are developing your stub, you need to be sure that it actually resembles your @@ -489,16 +504,31 @@ following section to your build:
Maven. }
Assume that you have written a test of your new feature. If a loan application for a big amount is received, the system should reject that loan application with some description.
Write the missing implementation.
At some point in time, you need to send a request to the Fraud Detection service. Assume that you need to send the request containing the ID of the client and the amount the -client wants to borrow. You want to send it to the
/fraudcheckurl via thePUTmethod.ResponseEntity<FraudServiceResponse> response = - restTemplate.exchange("http://localhost:" + port + "/fraudcheck", HttpMethod.PUT, - new HttpEntity<>(request, httpHeaders), - FraudServiceResponse.class);For simplicity, the port of the Fraud Detection service is set to
8080, and the +client wants to borrow. You want to send it to the/fraudcheckurl via thePUTmethod.ResponseEntity<FraudServiceResponse> response = restTemplate.exchange( + "http://localhost:" + port + "/fraudcheck", HttpMethod.PUT, + new HttpEntity<>(request, httpHeaders), FraudServiceResponse.class);For simplicity, the port of the Fraud Detection service is set to
8080, and the application runs on8090.If you start the test at this point, it breaks, because no service currently runs on port
8080.Clone the Fraud Detection service repository locally.
You can start by playing around with the server side contract. To do so, you must first clone it.
$ git clone https://your-git-server.com/server-side.git local-http-server-repoDefine the contract locally in the repo of Fraud Detection service.
As a consumer, you need to define what exactly you want to achieve. You need to formulate your expectations. To do so, write the following contract:
Important Place the contract under
src/test/resources/contracts/fraudfolder. Thefraudfolder is important because the producer’s test base class name references that folder.Groovy DSL. -
package contracts +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package contracts org.springframework.cloud.contract.spec.Contract.make { request { // (1) @@ -506,7 +536,7 @@ org.springframework.cloud.contract.spec.Contract.make { url '/fraudcheck' // (3) body([ // (4) "client.id": $(regex('[0-9]{10}')), - loanAmount: 99999 + loanAmount : 99999 ]) headers { // (5) contentType('application/json') @@ -515,7 +545,7 @@ org.springframework.cloud.contract.spec.Contract.make { response { // (6) status OK() // (7) body([ // (8) - fraudCheckStatus: "FRAUD", + fraudCheckStatus : "FRAUD", "rejection.reason": "Amount too high" ]) headers { // (9) @@ -676,8 +706,8 @@ Application service):Add the
Spring Cloud Co collaborators. (Optional step) Because you’re playing with the collaborators offline, you can also provide the offline work switch (StubRunnerProperties.StubsMode.LOCAL).@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = WebEnvironment.NONE) -@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:6565"}, - stubsMode = StubRunnerProperties.StubsMode.LOCAL) +@AutoConfigureStubRunner(ids = { + "com.example:http-server-dsl:+:stubs:6565" }, stubsMode = StubRunnerProperties.StubsMode.LOCAL) public class LoanApplicationServiceTests {Now, when you run your tests, you see something like this:
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 [] @@ -713,12 +743,29 @@ make the name of the base test class. In our case, the contracts were placed und thecontractsfolder, pick only one, which should befraud. Add theBasesuffix and capitalizefraud. That gives you theFraudBasetest class name.All the generated tests extend that class. Over there, you can set up your Spring Context or whatever is necessary. In this case, use Rest Assured MVC to -start the server side
FraudDetectionController.package com.example.fraud; +start the server sideFraudDetectionController./* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.fraud; import io.restassured.module.mockmvc.RestAssuredMockMvc; import org.junit.Before; public class FraudBase { + @Before public void setup() { RestAssuredMockMvc.standaloneSetup(new FraudDetectionController(), @@ -740,6 +787,7 @@ start the server sideFraudDetectionController. public void assertThatRejectionReasonIsNull(Object rejectionReason) { assert rejectionReason == null; } + }Now, if you run the
./mvnw clean install, you get something like this:Results : Tests in error: diff --git a/multi/multi__spring_cloud_contract_verifier_messaging.html b/multi/multi__spring_cloud_contract_verifier_messaging.html index ac737623be..bc9cf3db58 100644 --- a/multi/multi__spring_cloud_contract_verifier_messaging.html +++ b/multi/multi__spring_cloud_contract_verifier_messaging.html @@ -36,20 +36,20 @@ meanings for different messaging implementations. Fordestination of a channel. Then, if there is no such destinationit is resolved as a channel name. For Camel, that’s a certain component (for example,jms).For the given contract:
Groovy DSL. -
def contractDsl = Contract.make { - label 'some_label' - input { - triggeredBy('bookReturnedTriggered()') - } - outputMessage { - sentTo('activemq:output') - body('''{ "bookName" : "foo" }''') - headers { - header('BOOK-NAME', 'foo') - messagingContentType(applicationJson()) - } - } -}+
def contractDsl = Contract.make { + label 'some_label' + input { + triggeredBy('bookReturnedTriggered()') + } + outputMessage { + sentTo('activemq:output') + body('''{ "bookName" : "foo" }''') + headers { + header('BOOK-NAME', 'foo') + messagingContentType(applicationJson()) + } + } + }
YAML.
label: some_label input: @@ -61,7 +61,7 @@ outputMessage: headers: BOOK-NAME: foo contentType: application/json-
The following JUnit test is created:
''' +The following JUnit test is created:
''' // when: bookReturnedTriggered(); @@ -75,7 +75,7 @@ outputMessage: // and: DocumentContext parsedJson = JsonPath.parse(contractVerifierObjectMapper.writeValueAsString(response.getPayload())); assertThatJson(parsedJson).field("bookName").isEqualTo("foo"); -'''And the following Spock test would be created:
''' +'''And the following Spock test would be created:
''' when: bookReturnedTriggered() @@ -89,27 +89,27 @@ outputMessage: assertThatJson(parsedJson).field("bookName").isEqualTo("foo") '''For the given contract:
Groovy DSL. -
def contractDsl = Contract.make { - label 'some_label' - input { - messageFrom('jms:input') - messageBody([ - bookName: 'foo' - ]) - messageHeaders { - header('sample', 'header') - } - } - outputMessage { - sentTo('jms:output') - body([ - bookName: 'foo' - ]) - headers { - header('BOOK-NAME', 'foo') - } - } -}+
def contractDsl = Contract.make { + label 'some_label' + input { + messageFrom('jms:input') + messageBody([ + bookName: 'foo' + ]) + messageHeaders { + header('sample', 'header') + } + } + outputMessage { + sentTo('jms:output') + body([ + bookName: 'foo' + ]) + headers { + header('BOOK-NAME', 'foo') + } + } + }
YAML.
label: some_label input: @@ -124,7 +124,7 @@ outputMessage: bookName: foo headers: BOOK-NAME: foo-
The following JUnit test is created:
''' +The following JUnit test is created:
''' // given: ContractVerifierMessage inputMessage = contractVerifierMessaging.create( "{\\"bookName\\":\\"foo\\"}" @@ -142,7 +142,7 @@ outputMessage: // and: DocumentContext parsedJson = JsonPath.parse(contractVerifierObjectMapper.writeValueAsString(response.getPayload())); assertThatJson(parsedJson).field("bookName").isEqualTo("foo"); -'''And the following Spock test would be created:
"""\ +'''And the following Spock test would be created:
"""\ given: ContractVerifierMessage inputMessage = contractVerifierMessaging.create( '''{"bookName":"foo"}''', @@ -160,19 +160,19 @@ and: DocumentContext parsedJson = JsonPath.parse(contractVerifierObjectMapper.writeValueAsString(response.payload)) assertThatJson(parsedJson).field("bookName").isEqualTo("foo") """For the given contract:
Groovy DSL. -
def contractDsl = Contract.make { - label 'some_label' - input { - messageFrom('jms:delete') - messageBody([ - bookName: 'foo' - ]) - messageHeaders { - header('sample', 'header') - } - assertThat('bookWasDeleted()') - } -}+
def contractDsl = Contract.make { + label 'some_label' + input { + messageFrom('jms:delete') + messageBody([ + bookName: 'foo' + ]) + messageHeaders { + header('sample', 'header') + } + assertThat('bookWasDeleted()') + } + }
YAML.
label: some_label input: @@ -182,7 +182,7 @@ input: messageHeaders: sample: header assertThat: bookWasDeleted()-
The following JUnit test is created:
''' +The following JUnit test is created:
''' // given: ContractVerifierMessage inputMessage = contractVerifierMessaging.create( "{\\"bookName\\":\\"foo\\"}" @@ -194,7 +194,7 @@ input: // then: bookWasDeleted(); -'''And the following Spock test would be created:
''' +'''And the following Spock test would be created:
''' given: ContractVerifierMessage inputMessage = contractVerifierMessaging.create( \'\'\'{"bookName":"foo"}\'\'\', diff --git a/multi/multi__spring_cloud_contract_verifier_setup.html b/multi/multi__spring_cloud_contract_verifier_setup.html index 3fa864e5c5..b3c33f9f6a 100644 --- a/multi/multi__spring_cloud_contract_verifier_setup.html +++ b/multi/multi__spring_cloud_contract_verifier_setup.html @@ -176,7 +176,7 @@ name of the base class for the matched contract. You have to provide a list callcontractPackageRegextobaseClassFQNmapping. Consider the following example:baseClassForTests = "com.example.FooBase" baseClassMappings { baseClassMapping('.*/com/.*', 'com.example.ComBase') - baseClassMapping('.*/bar/.*':'com.example.BarBase') + baseClassMapping('.*/bar/.*': 'com.example.BarBase') }Let’s assume that you have contracts under -
src/test/resources/contract/com/-src/test/resources/contract/foo/By providing the
baseClassForTests, we have a fallback in case mapping did not succeed. diff --git a/multi/multi__spring_cloud_contract_wiremock.html b/multi/multi__spring_cloud_contract_wiremock.html index e9dff9d7a7..df973217a4 100644 --- a/multi/multi__spring_cloud_contract_wiremock.html +++ b/multi/multi__spring_cloud_contract_wiremock.html @@ -17,6 +17,12 @@ your test. The following code shows an example:< @Autowired private Service service; + @Before + public void setup() { + this.service.setBase("http://localhost:" + + this.environment.getProperty("wiremock.server.port")); + } + // Using the WireMock APIs in the normal way: @Test public void contextLoads() throws Exception { @@ -27,8 +33,7 @@ your test. The following code shows an example:< assertThat(this.service.go()).isEqualTo("Hello World!"); } -} -// end::wiremock_test2[]To start the stub server on a different port use (for example), +}
To start the stub server on a different port use (for example),
@AutoConfigureWireMock(port=9999). For a random port, use a value of0. The stub server port can be bound in the test application context with the "wiremock.server.port" property. Using@AutoConfigureWireMockadds a bean of typeWiremockConfigurationto @@ -76,15 +81,15 @@ instance, as shown in the following example:public static WireMockClassRule wiremock = new WireMockClassRule( WireMockSpring.options().dynamicPort()); + // A service that calls out over HTTP to wiremock's port + @Autowired + private Service service; + @Before public void setup() { this.service.setBase("http://localhost:" + wiremock.port()); } - // A service that calls out over HTTP to wiremock's port - @Autowired - private Service service; - // Using the WireMock APIs in the normal way: @Test public void contextLoads() throws Exception { @@ -95,8 +100,7 @@ instance, as shown in the following example:assertThat(this.service.go()).isEqualTo("Hello World!"); } -} -// end::wiremock_test2[]The
@ClassRulemeans that the server shuts down after all the methods in this class +}The
@ClassRulemeans that the server shuts down after all the methods in this class have been run.WireMock lets you stub a "secure" server with an "https" URL protocol. If your application wants to contact that stub server in an integration test, it will find that the SSL certificates are not valid (the usual problem with self-installed certificates). diff --git a/multi/multi__using_the_pluggable_architecture.html b/multi/multi__using_the_pluggable_architecture.html index 115ece5542..aaeb73feeb 100644 --- a/multi/multi__using_the_pluggable_architecture.html +++ b/multi/multi__using_the_pluggable_architecture.html @@ -12,7 +12,7 @@ structure converter. The following code listing shows the
* Converter to be used to convert FROM {@link File} TO {@link Contract} * and from {@link Contract} to {@code T} * - * @param <T> - type to which we want to convert the contract + * @param <T > - type to which we want to convert the contract * * @author Marcin Grzejszczak * @since 1.1.0 @@ -98,7 +98,7 @@ set ametaDataentry in the Pact file, with key"combine": "AND" } }, - "body" : { + "body": { "$.clientId": { "matchers": [ { @@ -233,15 +233,31 @@ current Pact version that you use. Maven.
testCompile "org.springframework.cloud:spring-cloud-contract-pact"
If you want to generate tests for languages other than Java or you are not happy with the way the verifier builds Java tests, you can register your own implementation.
The SingleTestGenerator interface lets you register your own implementation. The
-following code listing shows the SingleTestGenerator interface:
* - * @param properties - properties passed to the plugin - * @param listOfFiles - list of parsed contracts with additional metadata - * @param className - the name of the generated test class - * @param classPackage - the name of the package in which the test class should be stored - * @param includedDirectoryRelativePath - relative path to the included directory - * @return contents of a single test class - * @deprecated use {@link SingleTestGenerator#buildClass(ContractVerifierConfigProperties, Collection, String, GeneratedClassData)} - */ +following code listing shows theSingleTestGeneratorinterface:package org.springframework.cloud.contract.verifier.builder + + +import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties +import org.springframework.cloud.contract.verifier.file.ContractMetadata + +/** + * Builds a single test. + * + * @since 1.1.0 + */ +trait SingleTestGenerator { + + /** + * Creates contents of a single test class in which all test scenarios from + * the contract metadata should be placed. + * + * @param properties - properties passed to the plugin + * @param listOfFiles - list of parsed contracts with additional metadata + * @param className - the name of the generated test class + * @param classPackage - the name of the package in which the test class should be stored + * @param includedDirectoryRelativePath - relative path to the included directory + * @return contents of a single test class + * @deprecated use{@link SingleTestGenerator#buildClass(ContractVerifierConfigProperties, Collection, String, GeneratedClassData)} + */ @Deprecated abstract String buildClass(ContractVerifierConfigProperties properties, Collection<ContractMetadata> listOfFiles, String className, String classPackage, String includedDirectoryRelativePath) @@ -250,15 +266,18 @@ following code listing shows theSingleTestGeneratorString buildClass(ContractVerifierConfigProperties properties, Collection<ContractMetadata> listOfFiles, String includedDirectoryRelativePath, GeneratedClassData generatedClassData) { - return buildClass(properties, listOfFiles, generatedClassData.className, generatedClassData.classPackage, includedDirectoryRelativePath) + String className = generatedClassData.className + String classPackage = generatedClassData.classPackage + String path = includedDirectoryRelativePath + return buildClass(properties, listOfFiles, className, classPackage, path) } /** @@ -287,6 +306,7 @@ own implementation of theStubGeneratorinterface.StubGeneratorinterface:package org.springframework.cloud.contract.verifier.converter import groovy.transform.CompileStatic + import org.springframework.cloud.contract.spec.Contract import org.springframework.cloud.contract.verifier.file.ContractMetadata @@ -299,18 +319,18 @@ own implementation of theStubGeneratorinterface. interface StubGenerator { /** - * Returns {@code true} if the converter can handle the file to convert it into a stub. + * @return {@code true} if the converter can handle the file to convert it into a stub. */ boolean canHandleFileName(String fileName) /** - * Returns the collection of converted contracts into stubs. One contract can + * @return the collection of converted contracts into stubs. One contract can * result in multiple stubs. */ Map<Contract, String> convertContents(String rootName, ContractMetadata content) /** - * Returns the name of the converted stub file. If you have multiple contracts + * @return the name of the converted stub file. If you have multiple contracts * in a single file then a prefix will be added to the generated file. If you * provide the {@link Contract#name} field then that field will override the * generated file name. @@ -380,17 +400,19 @@ HTTP Stub server implementation, which might resemble the following example: @Override HttpServerStub registerMappings(Collection<File> stubFiles) { List<RunnerSetting> settings = stubFiles.findAll { it.name.endsWith("json") } - .collect { + .collect { log.info("Trying to parse [${it.name}]") try { - return RunnerSetting.aRunnerSetting().withStream(it.newInputStream()).build() - } catch (Exception e) { + return RunnerSetting.aRunnerSetting().withStream(it.newInputStream()). + build() + } + catch (Exception e) { log.warn("Exception occurred while trying to parse file [${it.name}]", e) return null } }.findAll { it } this.runner = JsonRunner.newJsonRunnerWithSetting(settings, - HttpArgs.httpArgs().withPort(this.port).build()) + HttpArgs.httpArgs().withPort(this.port).build()) this.runner.run() this.started = true return this diff --git a/multi/multi_contract-dsl.html b/multi/multi_contract-dsl.html index 90d955f9f8..7bd49d9805 100644 --- a/multi/multi_contract-dsl.html +++ b/multi/multi_contract-dsl.html @@ -65,8 +65,8 @@ You can also provide theassertJsonSizeproperty in properly when using the Groovy DSL and thevalue(consumer(…), producer(…))notation inGString. That is why you should use the Groovy Map notation.
The following sections describe the most common top-level elements:
You can add a description to your contract. The description is arbitrary text. The
following code shows an example:
Groovy DSL. -
org.springframework.cloud.contract.spec.Contract.make {
- description('''
+ org.springframework.cloud.contract.spec.Contract.make {
+ description('''
given:
An input
when:
@@ -74,7 +74,7 @@ when:
then:
Output
''')
- }
+
}
YAML.
description: Some description name: some name @@ -152,7 +152,23 @@ following resources in our project.└─ ├── readFromFile.groovy ├── request.json └── response.jsonFurther assume that your contract is as follows:
Groovy DSL. -
import org.springframework.cloud.contract.spec.Contract +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import org.springframework.cloud.contract.spec.Contract Contract.make { request { @@ -208,16 +224,16 @@ Contract.make { }
YAML.
request: - url: /1 - method: PUT - headers: - Content-Type: application/octet-stream - bodyFromFileAsBytes: request.pdf + url: /1 + method: PUT + headers: + Content-Type: application/octet-stream + bodyFromFileAsBytes: request.pdf response: - status: 200 - bodyFromFileAsBytes: response.pdf - headers: - Content-Type: application/octet-stream+ status: 200 + bodyFromFileAsBytes: response.pdf + headers: + Content-Type: application/octet-stream
Important You should use this approach whenever you want to work with binary payloads both for HTTP and messaging.
The following methods can be called in the top-level closure of a contract definition.
request and response are mandatory. priority is optional.
Groovy DSL.
org.springframework.cloud.contract.spec.Contract.make {
@@ -476,7 +492,7 @@ body:
Content-Type: multipart/form-data;boundary=AaB03x
multipart:
params:
- # key (parameter name), value (parameter value) pair
+ # key (parameter name), value (parameter value) pair
formParameter: '"formParameterValue"'
someBooleanParameter: true
named:
@@ -610,25 +626,25 @@ need to use patterns and not exact values both for your test and your server sid
}You can also provide only one side of the communication with a regular expression. If you do so, then the contract engine automatically provides the generated string that matches the provided regular expression. The following code shows an example:
org.springframework.cloud.contract.spec.Contract.make {
- request {
- method 'PUT'
- url value(consumer(regex('/foo/[0-9]{5}')))
- body([
- requestElement: $(consumer(regex('[0-9]{5}')))
- ])
- headers {
- header('header', $(consumer(regex('application\\/vnd\\.fraud\\.v1\\+json;.*'))))
- }
- }
- response {
- status OK()
- body([
- responseElement: $(producer(regex('[0-9]{7}')))
- ])
- headers {
- contentType("application/vnd.fraud.v1+json")
- }
- }
+ request {
+ method 'PUT'
+ url value(consumer(regex('/foo/[0-9]{5}')))
+ body([
+ requestElement: $(consumer(regex('[0-9]{5}')))
+ ])
+ headers {
+ header('header', $(consumer(regex('application\\/vnd\\.fraud\\.v1\\+json;.*'))))
+ }
+ }
+ response {
+ status OK()
+ body([
+ responseElement: $(producer(regex('[0-9]{7}')))
+ ])
+ headers {
+ contentType("application/vnd.fraud.v1+json")
+ }
+ }
}In the preceding example, the opposite side of the communication has the respective data generated for request and response.
Spring Cloud Contract comes with a series of predefined regular expressions that you can use in your contracts, as shown in the following example:
protected static final Pattern TRUE_OR_FALSE = Pattern.compile(/(true|false)/) @@ -639,21 +655,29 @@ use in your contracts, as shown in the following example:protected static final Pattern POSITIVE_INT = Pattern.compile('([1-9]\\d*)') protected static final Pattern DOUBLE = Pattern.compile('-?(\\d*\\.\\d+)') protected static final Pattern HEX = Pattern.compile('[a-fA-F0-9]+') -protected static final Pattern IP_ADDRESS = Pattern.compile('([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])') -protected static final Pattern HOSTNAME_PATTERN = Pattern.compile('((http[s]?|ftp):/)/?([^:/\\s]+)(:[0-9]{1,5})?') -protected static final Pattern EMAIL = Pattern.compile('[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6}') +protected static final Pattern IP_ADDRESS = Pattern. + compile('([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])') +protected static final Pattern HOSTNAME_PATTERN = Pattern. + compile('((http[s]?|ftp):/)/?([^:/\\s]+)(:[0-9]{1,5})?') +protected static final Pattern EMAIL = Pattern. + compile('[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6}') protected static final Pattern URL = UrlHelper.URL protected static final Pattern HTTPS_URL = UrlHelper.HTTPS_URL -protected static final Pattern UUID = Pattern.compile('[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}') -protected static final Pattern ANY_DATE = Pattern.compile('(\\d\\d\\d\\d)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])') -protected static final Pattern ANY_DATE_TIME = Pattern.compile('([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])') -protected static final Pattern ANY_TIME = Pattern.compile('(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])') +protected static final Pattern UUID = Pattern. + compile('[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}') +protected static final Pattern ANY_DATE = Pattern. + compile('(\\d\\d\\d\\d)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])') +protected static final Pattern ANY_DATE_TIME = Pattern. + compile('([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])') +protected static final Pattern ANY_TIME = Pattern. + compile('(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])') protected static final Pattern NON_EMPTY = Pattern.compile(/[\S\s]+/) protected static final Pattern NON_BLANK = Pattern.compile(/^\s*\S[\S\s]*/) -protected static final Pattern ISO8601_WITH_OFFSET = Pattern.compile(/([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(\.\d{3})?(Z|[+-][01]\d:[0-5]\d)/) +protected static final Pattern ISO8601_WITH_OFFSET = Pattern. + compile(/([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(\.\d{3})?(Z|[+-][01]\d:[0-5]\d)/) -protected static Pattern anyOf(String... values){ - return Pattern.compile(values.collect({"^$it\$"}).join("|")) +protected static Pattern anyOf(String... values) { + return Pattern.compile(values.collect({ "^$it\$" }).join("|")) } RegexProperty onlyAlphaUnicode() { @@ -704,7 +728,7 @@ RegexProperty httpsUrl() { return new RegexProperty(HTTPS_URL).asString() } -RegexProperty uuid(){ +RegexProperty uuid() { return new RegexProperty(UUID).asString() } @@ -731,28 +755,28 @@ RegexProperty nonEmpty() { RegexProperty nonBlank() { return new RegexProperty(NON_BLANK).asString() }In your contract, you can use it as shown in the following example:
Contract dslWithOptionalsInString = Contract.make { - priority 1 - request { - method POST() - url '/users/password' - headers { - contentType(applicationJson()) - } - body( - email: $(consumer(optional(regex(email()))), producer('abc@abc.com')), - callback_url: $(consumer(regex(hostname())), producer('http://partners.com')) - ) - } - response { - status 404 - headers { - contentType(applicationJson()) - } - body( - code: value(consumer("123123"), producer(optional("123123"))), - message: "User not found by email = [${value(producer(regex(email())), consumer('not.existing@user.com'))}]" - ) - } + priority 1 + request { + method POST() + url '/users/password' + headers { + contentType(applicationJson()) + } + body( + email: $(consumer(optional(regex(email()))), producer('abc@abc.com')), + callback_url: $(consumer(regex(hostname())), producer('http://partners.com')) + ) + } + response { + status 404 + headers { + contentType(applicationJson()) + } + body( + code: value(consumer("123123"), producer(optional("123123"))), + message: "User not found by email = [${value(producer(regex(email())), consumer('not.existing@user.com'))}]" + ) + } }To make matters even simpler you can use a set of predefined objects that will automatically assume that you want a regular expression to be passed. All of those methods start with
anyprefix:T anyAlphaUnicode() @@ -802,25 +826,25 @@ T anyOf(String... values)and this is an example of how you can referenc outputMessage { sentTo 'topic.rateablequote' body([ - alpha: $(anyAlphaUnicode()), - number: $(anyNumber()), - anInteger: $(anyInteger()), - positiveInt: $(anyPositiveInt()), - aDouble: $(anyDouble()), - aBoolean: $(aBoolean()), - ip: $(anyIpAddress()), - hostname: $(anyHostname()), - email: $(anyEmail()), - url: $(anyUrl()), - httpsUrl: $(anyHttpsUrl()), - uuid: $(anyUuid()), - date: $(anyDate()), - dateTime: $(anyDateTime()), - time: $(anyTime()), + alpha : $(anyAlphaUnicode()), + number : $(anyNumber()), + anInteger : $(anyInteger()), + positiveInt : $(anyPositiveInt()), + aDouble : $(anyDouble()), + aBoolean : $(aBoolean()), + ip : $(anyIpAddress()), + hostname : $(anyHostname()), + email : $(anyEmail()), + url : $(anyUrl()), + httpsUrl : $(anyHttpsUrl()), + uuid : $(anyUuid()), + date : $(anyDate()), + dateTime : $(anyDateTime()), + time : $(anyTime()), iso8601WithOffset: $(anyIso8601WithOffset()), - nonBlankString: $(anyNonBlankString()), - nonEmptyString: $(anyNonEmptyString()), - anyOf: $(anyOf('foo', 'bar')) + nonBlankString : $(anyNonBlankString()), + nonEmptyString : $(anyNonEmptyString()), + anyOf : $(anyOf('foo', 'bar')) ]) } }
![]() | Important |
|---|---|
This section is valid only for Groovy DSL. Check out the @@ -848,7 +872,7 @@ optional parameters only for the following: By wrapping a part of the body with the If you use Spock for, the following test would be generated from the previous example: """ +expression that must be present 0 or more times. The following is an example of a custom extension: TestWireMockExtensions.groovy. - package org.springframework.cloud.contract.verifier.dsl.wiremock +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.contract.verifier.dsl.wiremock import com.github.tomakehurst.wiremock.extension.Extension @@ -1399,7 +1439,7 @@ response: valueWithMinEmpty: [] valueWithMaxEmpty: [] key: - 'complex.key' : 'foo' + 'complex.key': 'foo' nulValue: null matchers: headers: @@ -1513,7 +1553,7 @@ separates the autogenerated assertions and the assertion from matchers):assertThatValueIsANumber. This method must be defined in the test base class or be statically imported to your tests. Notice that the |
The resulting WireMock stub is in the following example:
''' +the method name and passed the proper JSON path as a parameter to it.
The resulting WireMock stub is in the following example:
''' { "request" : { "urlPath" : "/get", @@ -1614,7 +1654,7 @@ asserted. In order to fix this, you should apply the assertion to the wholebyCommand(…) method.
The Spring Cloud Contract Verifier supports the JAX-RS 2 Client API. The base class needs
to define protected WebTarget webTarget and server initialization. The only option for
testing JAX-RS API is to start a web server. Also, a request with a body needs to have a
-content type set. Otherwise, the default of application/octet-stream gets used.
In order to use JAX-RS mode, use the following settings:
testMode == 'JAXRSCLIENT'The following example shows a generated test API:
''' +content type set. Otherwise, the default ofapplication/octet-streamgets used.In order to use JAX-RS mode, use the following settings:
testMode == 'JAXRSCLIENT'The following example shows a generated test API:
''' // when: Response response = webTarget .path("/users") @@ -1783,19 +1823,19 @@ as abodyMatchers { + xPath('/test/duck/text()', byRegex("[0-9]{3}")) + xPath('/test/duck/text()', byCommand('test($it)')) + xPath('/test/duck/xxx', byNull()) + xPath('/test/duck/text()', byEquality()) + xPath('/test/alpha/text()', byRegex(onlyAlphaUnicode())) + xPath('/test/alpha/text()', byEquality()) + xPath('/test/number/text()', byRegex(number())) + xPath('/test/date/text()', byDate()) + xPath('/test/dateTime/text()', byTimestamp()) + xPath('/test/time/text()', byTime()) + xPath('/test/*/complex/text()', byEquality()) + xPath('/test/duck/@type', byEquality()) + } + } + }StringorGStringboth request and response. In place of thejsonPath(…)method, theorg.springframework.cloud.contract.spec.internal.BodyMatchers.xPathmethod should be used, with the desiredxPathprovided as the first argument and the appropriateMatchingTypeas second. All the body matchers apart frombyType()are supported.Here is an example of a Groovy DSL contract with XML response body:
Contract.make { - request { - method GET() - urlPath '/get' - headers { - contentType(applicationXml()) - } - } - response { - status(OK()) - headers { - contentType(applicationXml()) - } - body """ + request { + method GET() + urlPath '/get' + headers { + contentType(applicationXml()) + } + } + response { + status(OK()) + headers { + contentType(applicationXml()) + } + body """ <test> <duck type='xtype'>123</duck> <alpha>abc</alpha> @@ -1812,22 +1852,22 @@ and the appropriateMatchingTypeas second. All the <valueWithoutAMatcher>foo</valueWithoutAMatcher> <key><complex>foo</complex></key> </test>""" - bodyMatchers { - xPath('/test/duck/text()', byRegex("[0-9]{3}")) - xPath('/test/duck/text()', byCommand('test($it)')) - xPath('/test/duck/xxx', byNull()) - xPath('/test/duck/text()', byEquality()) - xPath('/test/alpha/text()', byRegex(onlyAlphaUnicode())) - xPath('/test/alpha/text()', byEquality()) - xPath('/test/number/text()', byRegex(number())) - xPath('/test/date/text()', byDate()) - xPath('/test/dateTime/text()', byTimestamp()) - xPath('/test/time/text()', byTime()) - xPath('/test/*/complex/text()', byEquality()) - xPath('/test/duck/@type', byEquality()) - } - } - }And below is an example of a YAML contract with XML request and response bodies:
include::{verifier_core_path}/src/test/resources/yml/contract_rest_xml.ymlHere is an example of an automatically generated test for XML response body:
@Test +
And below is an example of a YAML contract with XML request and response bodies:
include::{verifier_core_path}/src/test/resources/yml/contract_rest_xml.ymlHere is an example of an automatically generated test for XML response body:
@Test public void validate_xmlMatches() throws Exception { // given: MockMvcRequestSpecification request = given() @@ -1908,7 +1948,7 @@ example:Groovy DSL. messageFrom('input') // has the following body messageBody([ - bookName: 'foo' + bookName: 'foo' ]) // and the following headers messageHeaders { @@ -1918,7 +1958,7 @@ example:
Groovy DSL. outputMessage { sentTo('output') body([ - bookName: 'foo' + bookName: 'foo' ]) headers { header('BOOK-NAME', 'foo') @@ -1957,24 +1997,24 @@ generates a test that sends the input message to the defined destination. On the use those paradigms in messaging. In addition, Spring Cloud Contract Verifier also provides the
consumerandproducermethods, as presented in the following example (note that you can use either$orvaluemethods to provideconsumerandproducer-parts):Contract.make { - label 'some_label' - input { - messageFrom value(consumer('jms:output'), producer('jms:input')) - messageBody([ - bookName: 'foo' - ]) - messageHeaders { - header('sample', 'header') - } - } - outputMessage { - sentTo $(consumer('jms:input'), producer('jms:output')) - body([ - bookName: 'foo' - ]) - } -}
In the input or outputMessage section you can call assertThat with the name
+parts):
Contract.make {
+ label 'some_label'
+ input {
+ messageFrom value(consumer('jms:output'), producer('jms:input'))
+ messageBody([
+ bookName: 'foo'
+ ])
+ messageHeaders {
+ header('sample', 'header')
+ }
+ }
+ outputMessage {
+ sentTo $(consumer('jms:input'), producer('jms:output'))
+ body([
+ bookName: 'foo'
+ ])
+ }
+ }In the input or outputMessage section you can call assertThat with the name
of a method (e.g. assertThatMessageIsOnTheQueue()) that you have defined in the
base class or in a static import. Spring Cloud Contract will execute that method
in the generated test.
You can define multiple contracts in one file. Such a contract might resemble the @@ -1982,25 +2022,25 @@ following example:
Groovy DSL.
import org.springframework.cloud.contract.spec.Contract [ - Contract.make { - name("should post a user") - request { - method 'POST' - url('/users/1') - } - response { - status OK() - } - }, - Contract.make { - request { - method 'POST' - url('/users/2') - } - response { - status OK() - } - } + Contract.make { + name("should post a user") + request { + method 'POST' + url('/users/1') + } + response { + status OK() + } + }, + Contract.make { + request { + method 'POST' + url('/users/2') + } + response { + status OK() + } + } ]
YAML.
---
@@ -2090,7 +2130,6 @@ testCompile 'org
Next you need to make some changes to your base class like the following example.
package com.example.fraud;
import io.restassured.module.mockmvc.RestAssuredMockMvc;
-
import org.junit.Before;
import org.junit.Rule;
import org.junit.rules.TestName;
@@ -2115,29 +2154,32 @@ testCompile 'org
@Rule
public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(OUTPUT);
- @Rule public TestName testName = new TestName();
+ @Rule
+ public TestName testName = new TestName();
@Autowired
private WebApplicationContext context;
@Before
public void setup() {
- RestAssuredMockMvc.mockMvc(MockMvcBuilders.webAppContextSetup(this.context)
- .apply(documentationConfiguration(this.restDocumentation))
- .alwaysDo(document(getClass().getSimpleName() + "_" + testName.getMethodName()))
- .build());
+ RestAssuredMockMvc.mockMvc(MockMvcBuilders.webAppContextSetup(this.context)
+ .apply(documentationConfiguration(this.restDocumentation))
+ .alwaysDo(document(
+ getClass().getSimpleName() + "_" + testName.getMethodName()))
+ .build());
}
protected void assertThatRejectionReasonIsNull(Object rejectionReason) {
assert rejectionReason == null;
}
-}
-// end::base_class[]
In case you are using the standalone setup, you can set up RestAssuredMockMvc like this:
package com.example.fraud;
+
+}In case you are using the standalone setup, you can set up RestAssuredMockMvc like this:
package com.example.fraud;
import io.restassured.module.mockmvc.RestAssuredMockMvc;
import org.junit.Before;
import org.junit.Rule;
import org.junit.rules.TestName;
+
import org.springframework.restdocs.JUnitRestDocumentation;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
@@ -2151,14 +2193,16 @@ testCompile 'org
@Rule
public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(OUTPUT);
- @Rule public TestName testName = new TestName();
+ @Rule
+ public TestName testName = new TestName();
@Before
public void setup() {
- RestAssuredMockMvc.standaloneSetup(MockMvcBuilders.standaloneSetup(new FraudDetectionController())
+ RestAssuredMockMvc.standaloneSetup(MockMvcBuilders
+ .standaloneSetup(new FraudDetectionController())
.apply(documentationConfiguration(this.restDocumentation))
- .alwaysDo(document(getClass().getSimpleName() + "_" + testName.getMethodName())));
+ .alwaysDo(document(
+ getClass().getSimpleName() + "_" + testName.getMethodName())));
}
-}
-// end::base_class[]
![[Tip]](images/tip.png)
Tip You don’t need to specify the output directory for the generated snippets since version 1.2.0.RELEASE of Spring REST Docs.