One of the issues that you might encounter while using Spring Cloud Contract Verifier is passing the generated WireMock JSON stubs from the server side to the client side (or to various clients). The same takes place in terms of client-side generation for messaging. Copying the JSON files and setting the client side for messaging manually is out of the question. That is why we introduced Spring Cloud Contract Stub Runner. It can automatically download and run the stubs for you. Add the additional snapshot repository to your Maven. <repositories> <repository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/snapshot</url> <snapshots> <enabled>true</enabled> </snapshots> </repository> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> <repository> <id>spring-releases</id> <name>Spring Releases</name> <url>https://repo.spring.io/release</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/snapshot</url> <snapshots> <enabled>true</enabled> </snapshots> </pluginRepository> <pluginRepository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </pluginRepository> <pluginRepository> <id>spring-releases</id> <name>Spring Releases</name> <url>https://repo.spring.io/release</url> <snapshots> <enabled>false</enabled> </snapshots> </pluginRepository> </pluginRepositories>
Gradle. buildscript { repositories { mavenCentral() mavenLocal() maven { url "http://repo.spring.io/snapshot" } maven { url "http://repo.spring.io/milestone" } maven { url "http://repo.spring.io/release" } }
The easiest approach would be to centralize the way stubs are kept. For example, you can keep them as jars in a Maven repository.
Maven. <!-- First disable the default jar setup in the properties section --> <!-- we don't want the verifier to do a jar for us --> <spring.cloud.contract.verifier.skip>true</spring.cloud.contract.verifier.skip> <!-- Next add the assembly plugin to your build --> <!-- we want the assembly plugin to generate the JAR --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <executions> <execution> <id>stub</id> <phase>prepare-package</phase> <goals> <goal>single</goal> </goals> <inherited>false</inherited> <configuration> <attach>true</attach> <descriptors> $../../../../src/assembly/stub.xml </descriptors> </configuration> </execution> </executions> </plugin> <!-- Finally setup your assembly. Below you can find the contents of src/main/assembly/stub.xml --> <assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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>stubs</id> <formats> <format>jar</format> </formats> <includeBaseDirectory>false</includeBaseDirectory> <fileSets> <fileSet> <directory>src/main/java</directory> <outputDirectory>/</outputDirectory> <includes> <include>**com/example/model/*.*</include> </includes> </fileSet> <fileSet> <directory>${project.build.directory}/classes</directory> <outputDirectory>/</outputDirectory> <includes> <include>**com/example/model/*.*</include> </includes> </fileSet> <fileSet> <directory>${project.build.directory}/snippets/stubs</directory> <outputDirectory>META-INF/${project.groupId}/${project.artifactId}/${project.version}/mappings</outputDirectory> <includes> <include>**/*</include> </includes> </fileSet> <fileSet> <directory>$../../../../src/test/resources/contracts</directory> <outputDirectory>META-INF/${project.groupId}/${project.artifactId}/${project.version}/contracts</outputDirectory> <includes> <include>**/*.groovy</include> </includes> </fileSet> </fileSets> </assembly>
Gradle. ext { contractsDir = file("mappings") stubsOutputDirRoot = file("${project.buildDir}/production/${project.name}-stubs/") } // Automatically added by plugin: // copyContracts - copies contracts to the output folder from which JAR will be created // verifierStubsJar - JAR with a provided stub suffix // the presented publication is also added by the plugin but you can modify it as you wish publishing { publications { stubs(MavenPublication) { artifactId "${project.name}-stubs" artifact verifierStubsJar } } }
Runs stubs for service collaborators. Treating stubs as contracts of services allows to use stub-runner as an implementation of Consumer Driven Contracts. Stub Runner allows you to automatically download the stubs of the provided dependencies (or pick those from the classpath), start WireMock servers for them and feed them with proper stub definitions. For messaging, special stub routes are defined. You can pick the following options of acquiring stubs
The latter example is described in the Custom Stub Runner section. You can control the stub downloading via the
Example: @AutoConfigureStubRunner(repositoryRoot="http://foo.bar", ids = "com.example:beer-api-producer:+:stubs:8095", stubsMode = StubRunnerProperties.StubsMode.LOCAL) If you set the @AutoConfigureStubRunner(ids = { "com.example:beer-api-producer:+:stubs:8095", "com.example.foo:bar:1.0.0:superstubs:8096" }) If you’ve added the dependencies to your classpath Maven. <dependency> <groupId>com.example</groupId> <artifactId>beer-api-producer-restdocs</artifactId> <classifier>stubs</classifier> <version>0.0.1-SNAPSHOT</version> <scope>test</scope> <exclusions> <exclusion> <groupId>*</groupId> <artifactId>*</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>com.example.foo</groupId> <artifactId>bar</artifactId> <classifier>superstubs</classifier> <version>1.0.0</version> <scope>test</scope> <exclusions> <exclusion> <groupId>*</groupId> <artifactId>*</artifactId> </exclusion> </exclusions> </dependency>
Gradle. testCompile("com.example:beer-api-producer-restdocs:0.0.1-SNAPSHOT:stubs") { transitive = false } testCompile("com.example.foo:bar:1.0.0:superstubs") { transitive = false }
Then the following locations on your classpath will get scanned. For
and
The producer would setup the contracts like this: └── src
└── test
└── resources
└── contracts
└── com.example
└── beer-api-producer-restdocs
└── nested
└── contract3.groovy
To achieve proper stub packaging. Or using the Maven └── META-INF └── com.example └── beer-api-producer-restdocs └── 2.0.0 ├── contracts │ └── nested │ └── contract2.groovy └── mappings └── mapping.json By maintaining this structure classpath gets scanned and you can profit from the messaging / HTTP stubs without the need to download artifacts. Stub Runner has a notion of a Spring Cloud Contract Stub Runner comes with an implementation that you
can extend, for WireMock - WireMockHttpServerStubConfigurer implementation. @CompileStatic static class HttpsForFraudDetection extends WireMockHttpServerStubConfigurer { private static final Log log = LogFactory.getLog(HttpsForFraudDetection) @Override WireMockConfiguration configure(WireMockConfiguration httpStubConfiguration, HttpServerStubConfiguration httpServerStubConfiguration) { if (httpServerStubConfiguration.stubConfiguration.artifactId == "fraudDetectionServer") { int httpsPort = SocketUtils.findAvailableTcpPort() log.info("Will set HTTPs port [" + httpsPort + "] for fraud detection server") return httpStubConfiguration .httpsPort(httpsPort) } return httpStubConfiguration } }
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' (default: stubs) --maxPort, --maxp <Integer> Maximum port value to be assigned to the WireMock instance. Defaults to 15000 (default: 15000) --minPort, --minp <Integer> Minimum port value to be assigned to the WireMock instance. Defaults to 10000 (default: 10000) -p, --password Password to user when connecting to repository --phost, --proxyHost Proxy host to use for repository requests --pport, --proxyPort [Integer] Proxy port to use for repository requests -r, --root Location of a Jar containing server where you keep your stubs (e.g. http: //nexus. net/content/repositories/repository) -s, --stubs Comma separated list of Ivy representation of jars with stubs. Eg. groupid:artifactid1,groupid2: artifactid2:classifier --sm, --stubsMode Stubs mode to be used. Acceptable values [CLASSPATH, LOCAL, REMOTE] -u, --username Username to user when connecting to repository Stubs are defined in JSON documents, whose syntax is defined in WireMock documentation Example: { "request": { "method": "GET", "url": "/ping" }, "response": { "status": 200, "body": "pong", "headers": { "Content-Type": "text/plain" } } } Every stubbed collaborator exposes list of defined mappings under You can also use the @AutoConfigureStubRunner(ids="a.b.c:loanIssuance,a.b.c:fraudDetectionServer", mappingsOutputFolder = "target/outputmappings/") and for the JUnit approach like this: @ClassRule @Shared StubRunnerRule rule = new StubRunnerRule() .repoRoot("http://some_url") .downloadStub("a.b.c", "loanIssuance") .downloadStub("a.b.c:fraudDetectionServer") .withMappingsOutputFolder("target/outputmappings") Then if you check out the folder . ├── fraudDetectionServer_13705 └── loanIssuance_12255 That means that there were two stubs registered. [{ "id" : "f9152eb9-bf77-4c38-8289-90be7d10d0d7", "request" : { "url" : "/name", "method" : "GET" }, "response" : { "status" : 200, "body" : "fraudDetectionServer" }, "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()) .stubsMode(StubRunnerProperties.StubsMode.REMOTE) .downloadStub("org.springframework.cloud.contract.verifier.stubs", "loanIssuance") .downloadStub("org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer"); There’s also a
Stub Runner uses Eclipse Aether mechanism to download the Maven dependencies. Check their docs for more information. Since the package org.springframework.cloud.contract.stubrunner; import java.net.URL; import java.util.Collection; import java.util.Map; import org.springframework.cloud.contract.spec.Contract; public interface StubFinder extends StubTrigger { /** * For the given groupId and artifactId tries to find the matching URL of the running * stub. * @param groupId - might be null. In that case a search only via artifactId takes * place * @return URL of a running stub or throws exception if not found */ URL findStubUrl(String groupId, String artifactId) throws StubNotFoundException; /** * For the given Ivy notation {@code [groupId]:artifactId:[version]:[classifier]} * tries to find the matching URL of the running stub. You can also pass only * {@code artifactId}. * @param ivyNotation - Ivy representation of the Maven artifact * @return URL of a running stub or throws exception if not found */ URL findStubUrl(String ivyNotation) throws StubNotFoundException; /** * Returns all running stubs */ RunningStubs findAllRunningStubs(); /** * Returns the list of Contracts */ Map<StubConfiguration, Collection<Contract>> getContracts(); } 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") .downloadStub("org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer") .withMappingsOutputFolder("target/outputmappingsforrule") def 'should start WireMock servers'() { expect: 'WireMocks are running' rule.findStubUrl('org.springframework.cloud.contract.verifier.stubs', 'loanIssuance') != null rule.findStubUrl('loanIssuance') != null rule.findStubUrl('loanIssuance') == rule.findStubUrl('org.springframework.cloud.contract.verifier.stubs', 'loanIssuance') rule.findStubUrl('org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer') != null and: rule.findAllRunningStubs().isPresent('loanIssuance') rule.findAllRunningStubs().isPresent('org.springframework.cloud.contract.verifier.stubs', 'fraudDetectionServer') rule.findAllRunningStubs().isPresent('org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer') and: 'Stubs were registered' "${rule.findStubUrl('loanIssuance').toString()}/name".toURL().text == 'loanIssuance' "${rule.findStubUrl('fraudDetectionServer').toString()}/name".toURL().text == 'fraudDetectionServer' } def 'should output mappings to output folder'() { when: 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(); 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.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"); } 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"); @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(); } Check the Common properties for JUnit and Spring for more information on how to apply global configuration of Stub Runner.
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 @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 and registers in WireMock the selected stubs. If you want to find the URL of your stubbed dependency you can autowire the @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) @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 @BeforeClass @AfterClass void setupProps() { System.clearProperty("stubrunner.repository.root") System.clearProperty("stubrunner.classifier") } def 'should start WireMock servers'() { expect: 'WireMocks are running' stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs', 'loanIssuance') != null stubFinder.findStubUrl('loanIssuance') != null stubFinder.findStubUrl('loanIssuance') == stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs', 'loanIssuance') stubFinder.findStubUrl('loanIssuance') == stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs:loanIssuance') stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs:loanIssuance:0.0.1-SNAPSHOT') == stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs:loanIssuance:0.0.1-SNAPSHOT:stubs') stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer') != null and: stubFinder.findAllRunningStubs().isPresent('loanIssuance') stubFinder.findAllRunningStubs().isPresent('org.springframework.cloud.contract.verifier.stubs', 'fraudDetectionServer') stubFinder.findAllRunningStubs().isPresent('org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer') and: 'Stubs were registered' "${stubFinder.findStubUrl('loanIssuance').toString()}/name".toURL().text == 'loanIssuance' "${stubFinder.findStubUrl('fraudDetectionServer').toString()}/name".toURL().text == 'fraudDetectionServer' and: 'Fraud Detection is an HTTPS endpoint' stubFinder.findStubUrl('fraudDetectionServer').toString().startsWith("https") } def 'should throw an exception when stub is not found'() { when: stubFinder.findStubUrl('nonExistingService') then: thrown(StubNotFoundException) when: stubFinder.findStubUrl('nonExistingGroupId', 'nonExistingArtifactId') then: thrown(StubNotFoundException) } def 'should register started servers as environment variables'() { expect: environment.getProperty("stubrunner.runningstubs.loanIssuance.port") != null stubFinder.findAllRunningStubs().getPort("loanIssuance") == (environment.getProperty("stubrunner.runningstubs.loanIssuance.port") as Integer) and: environment.getProperty("stubrunner.runningstubs.fraudDetectionServer.port") != null stubFinder.findAllRunningStubs().getPort("fraudDetectionServer") == (environment.getProperty("stubrunner.runningstubs.fraudDetectionServer.port") as Integer) and: environment.getProperty("stubrunner.runningstubs.fraudDetectionServer.port") != null stubFinder.findAllRunningStubs().getPort("fraudDetectionServer") == (environment.getProperty("stubrunner.runningstubs.org.springframework.cloud.contract.verifier.stubs.fraudDetectionServer.port") as Integer) } def 'should be able to interpolate a running stub in the passed test property'() { given: int fraudPort = stubFinder.findAllRunningStubs().getPort("fraudDetectionServer") expect: fraudPort > 0 environment.getProperty("foo", Integer) == fraudPort environment.getProperty("fooWithGroup", Integer) == fraudPort foo == fraudPort } @Issue("#573") def 'should be able to retrieve the port of a running stub via an annotation'() { given: int fraudPort = stubFinder.findAllRunningStubs().getPort("fraudDetectionServer") expect: fraudPort > 0 fraudDetectionServerPort == fraudPort fraudDetectionServerPortWithGroupId == fraudPort } def 'should dump all mappings to a file'() { when: def url = stubFinder.findStubUrl("fraudDetectionServer") then: new File("target/outputmappings/", "fraudDetectionServer_${url.port}").exists() } @Configuration @EnableAutoConfiguration static class Config {} @CompileStatic static class HttpsForFraudDetection extends WireMockHttpServerStubConfigurer { private static final Log log = LogFactory.getLog(HttpsForFraudDetection) @Override WireMockConfiguration configure(WireMockConfiguration httpStubConfiguration, HttpServerStubConfiguration httpServerStubConfiguration) { if (httpServerStubConfiguration.stubConfiguration.artifactId == "fraudDetectionServer") { int httpsPort = SocketUtils.findAvailableTcpPort() log.info("Will set HTTPs port [" + httpsPort + "] for fraud detection server") return httpStubConfiguration .httpsPort(httpsPort) } return httpStubConfiguration } } } for the following configuration file: stubrunner: repositoryRoot: classpath:m2repo/repository/ ids: - org.springframework.cloud.contract.verifier.stubs:loanIssuance - org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer - org.springframework.cloud.contract.verifier.stubs:bootService stubs-mode: remote Instead of using the properties you can also use the properties inside the @AutoConfigureStubRunner( ids = ["org.springframework.cloud.contract.verifier.stubs:loanIssuance", "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
Which you can reference in your code. You can also use the @StubRunnerPort("foo") int fooPort; @StubRunnerPort("com.example:bar") int barPort; Stub Runner can integrate with Spring Cloud. For real life examples you can check the The most important feature of
that means that regardless of the fact whether you’re using Zookeeper, Consul, Eureka or anything
else, you don’t need that in your tests.
We’re starting WireMock instances of your dependencies and we’re telling your application
whenever you’re using For example this test will pass def 'should make service discovery work'() { expect: 'WireMocks are running' "${stubFinder.findStubUrl('loanIssuance').toString()}/name".toURL().text == 'loanIssuance' "${stubFinder.findStubUrl('fraudDetectionServer').toString()}/name".toURL().text == 'fraudDetectionServer' and: 'Stubs can be reached via load service discovery' restTemplate.getForObject('http://loanIssuance/name', String) == 'loanIssuance' restTemplate.getForObject('http://someNameThatShouldMapFraudDetectionServer/name', String) == 'fraudDetectionServer' } for the following configuration file stubrunner: idsToServiceIds: ivyNotation: someValueInsideYourCode 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 //Hack to work around https://github.com/spring-cloud/spring-cloud-commons/issues/156 static { System.setProperty("eureka.client.enabled", "false"); System.setProperty("spring.cloud.config.failFast", "false"); } You can match the artifactId of the stub with the name of your app by using the
The default Maven configuration used by Stub Runner can be tweaked either via the following system properties or environment variables
Spring Cloud Contract Stub Runner Boot is a Spring Boot application that exposes REST endpoints to trigger the messaging labels and to access started WireMock servers. One of the use-cases is to run some smoke (end to end) tests on a deployed application. You can check out the Spring Cloud Pipelines project for more information. Just add the compile "org.springframework.cloud:spring-cloud-starter-stub-runner"
Annotate a class with For the properties check the Stub Runner Spring section. You can download a standalone JAR from Maven (e.g. for version 2.0.1.RELEASE), as follows: $ wget -O stub-runner.jar 'https://search.maven.org/remotecontent?filepath=org/springframework/cloud/spring-cloud-contract-stub-runner-boot/2.0.1.RELEASE/spring-cloud-contract-stub-runner-boot-2.0.1.RELEASE.jar'
$ java -jar stub-runner.jar --stubrunner.ids=... --stubrunner.repositoryRoot=...
Starting from In order to pass the configuration just create a stubrunner.yml. stubrunner: stubsMode: LOCAL ids: - com.example:beer-api-producer:+:9876
and then just call
For Messaging
@ContextConfiguration(classes = StubRunnerBoot, loader = SpringBootContextLoader) @SpringBootTest(properties = "spring.cloud.zookeeper.enabled=false") @ActiveProfiles("test") class StubRunnerBootSpec extends Specification { @Autowired StubRunning stubRunning def setup() { RestAssuredMockMvc.standaloneSetup(new HttpStubsController(stubRunning), new TriggerController(stubRunning)) } def 'should return a list of running stub servers in "full ivy:port" notation'() { when: String response = RestAssuredMockMvc.get('/stubs').body.asString() then: def root = new JsonSlurper().parseText(response) root.'org.springframework.cloud.contract.verifier.stubs:bootService:0.0.1-SNAPSHOT:stubs' instanceof Integer } def 'should return a port on which a [#stubId] stub is running'() { when: def response = RestAssuredMockMvc.get("/stubs/${stubId}") then: response.statusCode == 200 Integer.valueOf(response.body.asString()) > 0 where: stubId << ['org.springframework.cloud.contract.verifier.stubs:bootService:+:stubs', 'org.springframework.cloud.contract.verifier.stubs:bootService:0.0.1-SNAPSHOT:stubs', 'org.springframework.cloud.contract.verifier.stubs:bootService:+', 'org.springframework.cloud.contract.verifier.stubs:bootService', 'bootService'] } def 'should return 404 when missing stub was called'() { when: def response = RestAssuredMockMvc.get("/stubs/a:b:c:d") then: response.statusCode == 404 } def 'should return a list of messaging labels that can be triggered when version and classifier are passed'() { when: 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"]) } def 'should trigger a messaging label'() { given: StubRunning stubRunning = Mock() RestAssuredMockMvc.standaloneSetup(new HttpStubsController(stubRunning), new TriggerController(stubRunning)) when: def response = RestAssuredMockMvc.post("/triggers/delete_book") then: response.statusCode == 200 and: 1 * stubRunning.trigger('delete_book') } def 'should trigger a messaging label for a stub with [#stubId] ivy notation'() { given: StubRunning stubRunning = Mock() RestAssuredMockMvc.standaloneSetup(new HttpStubsController(stubRunning), new TriggerController(stubRunning)) when: def response = RestAssuredMockMvc.post("/triggers/$stubId/delete_book") then: response.statusCode == 200 and: 1 * stubRunning.trigger(stubId, 'delete_book') where: stubId << ['org.springframework.cloud.contract.verifier.stubs:bootService:stubs', 'org.springframework.cloud.contract.verifier.stubs:bootService', 'bootService'] } def 'should throw exception when trigger is missing'() { when: RestAssuredMockMvc.post("/triggers/missing_label") then: Exception e = thrown(Exception) e.message.contains("Exception occurred while trying to return [missing_label] label.") e.message.contains("Available labels are") e.message.contains("org.springframework.cloud.contract.verifier.stubs:loanIssuance:0.0.1-SNAPSHOT:stubs=[]") e.message.contains("org.springframework.cloud.contract.verifier.stubs:bootService:0.0.1-SNAPSHOT:stubs=") } } One of the possibilities of using Stub Runner Boot is to use it as a feed of stubs for "smoke-tests". What does it mean? Let’s assume that you don’t want to deploy 50 microservice to a test environment in order to check if your application is working fine. You’ve already executed a suite of tests during the build process but you would also like to ensure that the packaging of your application is fine. What you can do is to deploy your application to an environment, start it and run a couple of tests on it to see if it’s working fine. We can call those tests smoke-tests since their idea is to check only a handful of testing scenarios. The problem with this approach is such that if you’re doing microservices most likely you’re using a service discovery tool. Stub Runner Boot allows you to solve this issue by starting the required stubs and register them in a service discovery tool. Let’s take a look at an example of such a setup with Eureka. Let’s assume that Eureka was already running. @SpringBootApplication @EnableStubRunnerServer @EnableEurekaClient @AutoConfigureStubRunner public class StubRunnerBootEurekaExample { public static void main(String[] args) { SpringApplication.run(StubRunnerBootEurekaExample.class, args); } } As you can see we want to start a Stub Runner Boot server 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 -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 mapping That 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 There are cases in which 2 consumers of the same endpoint want to have 2 different responses.
Let’s look at the following example for contract defined for the producer called Consumer request { url '/foo' method GET() } response { status OK() body( foo: "foo" } } Consumer request { url '/foo' method GET() } response { status OK() body( bar: "bar" } } You can’t produce for the same request 2 different responses. That’s why you can properly package
the
contracts and then profit from the On the producer side the consumers can have a folder that contains contracts related only to them.
By setting the On the . └── contracts ├── bar-consumer │ ├── bookReturnedForBar.groovy │ └── shouldCallBar.groovy └── foo-consumer ├── bookReturnedForFoo.groovy └── shouldCallFoo.groovy Being the @ContextConfiguration(classes = Config, loader = SpringBootContextLoader) @SpringBootTest(properties = ["spring.application.name=bar-consumer"]) @AutoConfigureStubRunner(ids = "org.springframework.cloud.contract.verifier.stubs:producerWithMultipleConsumers", repositoryRoot = "classpath:m2repo/repository/", stubsMode = StubRunnerProperties.StubsMode.REMOTE, stubsPerConsumer = true) class StubRunnerStubsPerConsumerSpec extends Specification { ... } Then only the stubs registered under a path that contains the Or set the consumer name explicitly @ContextConfiguration(classes = Config, loader = SpringBootContextLoader) @SpringBootTest @AutoConfigureStubRunner(ids = "org.springframework.cloud.contract.verifier.stubs:producerWithMultipleConsumers", repositoryRoot = "classpath:m2repo/repository/", consumerName = "foo-consumer", stubsMode = StubRunnerProperties.StubsMode.REMOTE, stubsPerConsumer = true) class StubRunnerStubsPerConsumerWithConsumerNameSpec extends Specification { ... } Then only the stubs registered under a path that contains the You can check out issue 224 for more information about the reasons behind this change. This section briefly describes common properties, including: You can set repetitive properties by using system properties or Spring configuration properties. Here are their names with their default values:
You can provide the stubs to download via the groupId:artifactId:version:classifier:port Note that
We’re publishing a If you want to learn more about the basics of Maven, artifact ids, group ids, classifiers and Artifact Managers, just click here Section 89.5, “Docker Project”. Just execute the docker image. You can pass any of the Section 91.8.1,
“Common Properties for JUnit and Spring”
as environment variables. The convention is that all the
letters should be upper case. The camel case notation should
and the dot ( We’d like to use the stubs created in this Section 89.5.4,
“Server side (nodejs)” step.
Let’s assume that we want to run the stubs on port $ git clone https://github.com/spring-cloud-samples/spring-cloud-contract-nodejs
$ cd bookstore
Let’s run the Stub Runner Boot application with the stubs. # Provide the Spring Cloud Contract Docker version $ SC_CONTRACT_DOCKER_VERSION="..." # The IP at which the app is running and Docker container can reach it $ APP_IP="192.168.0.100" # Spring Cloud Contract Stub Runner properties $ STUBRUNNER_PORT="8083" # Stub coordinates 'groupId:artifactId:version:classifier:port' $ STUBRUNNER_IDS="com.example:bookstore:0.0.1.RELEASE:stubs:9876" $ STUBRUNNER_REPOSITORY_ROOT="http://${APP_IP}:8081/artifactory/libs-release-local" # Run the docker with Stub Runner Boot $ docker run --rm -e "STUBRUNNER_IDS=${STUBRUNNER_IDS}" -e "STUBRUNNER_REPOSITORY_ROOT=${STUBRUNNER_REPOSITORY_ROOT}" -e "STUBRUNNER_STUBS_MODE=REMOTE" -p "${STUBRUNNER_PORT}:${STUBRUNNER_PORT}" -p "9876:9876" springcloud/spring-cloud-contract-stub-runner:"${SC_CONTRACT_DOCKER_VERSION}" What’s happening is that
On the server side we built a stateful stub. Let’s use curl to assert that the stubs are setup properly. # let's execute the first request (no response is returned) $ curl -H "Content-Type:application/json" -X POST --data '{ "title" : "Title", "genre" : "Genre", "description" : "Description", "author" : "Author", "publisher" : "Publisher", "pages" : 100, "image_url" : "https://d213dhlpdb53mu.cloudfront.net/assets/pivotal-square-logo-41418bd391196c3022f3cd9f3959b3f6d7764c47873d858583384e759c7db435.svg", "buy_url" : "https://pivotal.io" }' http://localhost:9876/api/books # Now time for the second request $ curl -X GET http://localhost:9876/api/books # You will receive contents of the JSON
|