@AutoConfigureStubRunner(repositoryRoot="http://foo.bar", ids = "com.example:beer-api-producer:+:stubs:8095")
+diff --git a/spring-cloud-contract.html b/spring-cloud-contract.html index f9e189cc62..01f6b31955 100644 --- a/spring-cloud-contract.html +++ b/spring-cloud-contract.html @@ -638,6 +638,12 @@ $(addBlockSwitches);
Stub Runner allows you to automatically download the stubs of the provided dependencies, start WireMock servers for them and feed them with proper stub definitions. +
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
+Aether based solution that downloads JARs with stubs from Artifactory / Nexus
+Classpath scanning solution that searches classpath via pattern to retrieve stubs
+Write your own implementation of the org.springframework.cloud.contract.stubrunner.StubDownloaderBuilder for full customization
The latter example is described in the Custom Stub Runner section.
+If you provide the stubrunner.repositoryRoot or stubrunner.workOffline flag will be set
+to true then Stub Runner will connect to the given server and download the required jars.
+It will then unpack the JAR to a temporary folder and reference those files in further
+contract processing.
Example:
+@AutoConfigureStubRunner(repositoryRoot="http://foo.bar", ids = "com.example:beer-api-producer:+:stubs:8095")
+If you DON’T provide the stubrunner.repositoryRoot or stubrunner.workOffline flag will
+be set to false (that’s the default) then classpath will get scanned. Let’s look at the
+following example:
@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
+<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>
+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 com.example:beer-api-producer-restdocs
/META-INF/com.example/beer-api-producer-restdocs/*/.*
+/contracts/com.example/beer-api-producer-restdocs/*/.*
+/mappings/com.example/beer-api-producer-restdocs/*/.*
+and com.example.foo:bar
/META-INF/com.example.foo/bar/*/.*
+/contracts/com.example.foo/bar/*/.*
+/mappings/com.example.foo/bar/*/.*
+|
+ Tip
+ |
++As you can see you have to explicitly provide the group and artifact ids when packaging the +producer stubs. + | +
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 assembly plugin or
+Gradle Jar task you have to create the following
+structure in your stubs jar.
└── 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.
+# Example of a custom HTTP Server Stub
-org.springframework.cloud.contract.stubrunner.HttpServerStub=\
+org.springframework.cloud.contract.stubrunner.HttpServerStub=\
org.springframework.cloud.contract.stubrunner.provider.moco.MocoHttpServerStub
You can customize the way your stubs are downloaded. If you don’t want to download the JARs
-from Nexus / Artifactory in the way we do by default you can set your own implementation.
-Below you can find an example of a Stub Downloader Provider that takes json files from the test resources
-from classpath, copies them to a temp file and then passes that temporary folder
- as a root for the stubs.
You can customize the way your stubs are downloaded. It’s enough to create an
+implementation of the StubDownloaderBuilder
package org.springframework.cloud.contract.stubrunner.provider.moco
+package com.example;
-import org.springframework.cloud.contract.stubrunner.StubConfiguration
-import org.springframework.cloud.contract.stubrunner.StubDownloader
-import org.springframework.cloud.contract.stubrunner.StubDownloaderBuilder
-import org.springframework.cloud.contract.stubrunner.StubRunnerOptions
-import org.springframework.core.io.DefaultResourceLoader
-import org.springframework.core.io.Resource
-import org.springframework.core.io.support.PathMatchingResourcePatternResolver
-
-import java.nio.file.Files
-
-/**
- * Poor man's version of taking stubs from classpath. It needs much more
- * love and attention to go to the main sources.
- *
- * @author Marcin Grzejszczak
- */
-class ClasspathStubProvider implements StubDownloaderBuilder {
-
- private static final int TEMP_DIR_ATTEMPTS = 10000
+class CustomStubDownloaderBuilder implements StubDownloaderBuilder {
@Override
- public StubDownloader build(StubRunnerOptions stubRunnerOptions) {
- final StubConfiguration configuration = stubRunnerOptions.getDependencies().first()
- PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(
- new DefaultResourceLoader())
- try {
- String rootFolder = repoRoot(stubRunnerOptions) ?: "**/" + separatedArtifact(configuration) + "/**/*.json"
- Resource[] resources = resolver.getResources(rootFolder)
- final File tmp = createTempDir()
- tmp.deleteOnExit()
- // you'd have to write an impl to maintain the folder structure
- // this is just for demo
- resources.each { Resource resource ->
- Files.copy(resource.getInputStream(), new File(tmp, resource.getFile().getName()).toPath())
+ public StubDownloader build(final StubRunnerOptions stubRunnerOptions) {
+ return new StubDownloader() {
+ @Override
+ public Map.Entry<StubConfiguration, File> downloadAndUnpackStubJar(
+ StubConfiguration config) {
+ File unpackedStubs = retrieveStubs();
+ return new AbstractMap.SimpleEntry<>(
+ new StubConfiguration(config.getGroupId(), config.getArtifactId(), version,
+ config.getClassifier()), unpackedStubs);
}
- return new StubDownloader() {
- @Override
- public Map.Entry<StubConfiguration, File> downloadAndUnpackStubJar(
- StubConfiguration stubConfiguration) {
- return new AbstractMap.SimpleEntry(configuration, tmp)
- }
+
+ File retrieveStubs() {
+ // here goes your custom logic to provide a folder where all the stubs reside
}
- } catch (IOException e) {
- throw new IllegalStateException(e)
- }
- }
-
- private String repoRoot(StubRunnerOptions stubRunnerOptions) {
- switch (stubRunnerOptions.stubRepositoryRoot) {
- case { !it }:
- return ""
- case { String root -> root.endsWith("**/*.json") }:
- return stubRunnerOptions.stubRepositoryRoot
- default:
- return stubRunnerOptions.stubRepositoryRoot + "/**/*.json"
- }
- }
-
- private String separatedArtifact(StubConfiguration configuration) {
- return configuration.getGroupId().replace(".", File.separator) +
- File.separator + configuration.getArtifactId()
- }
-
- // Taken from Guava
- private File createTempDir() {
- File baseDir = new File(System.getProperty("java.io.tmpdir"))
- String baseName = System.currentTimeMillis() + "-"
- for (int counter = 0; counter < TEMP_DIR_ATTEMPTS; counter++) {
- File tempDir = new File(baseDir, baseName + counter)
- if (tempDir.mkdir()) {
- return tempDir
- }
- }
- throw new IllegalStateException(
- "Failed to create directory within " + TEMP_DIR_ATTEMPTS + " attempts (tried " + baseName + "0 to " + baseName + (
- TEMP_DIR_ATTEMPTS - 1) + ")")
- }
}
# Example of a custom Stub Downloader Provider
org.springframework.cloud.contract.stubrunner.StubDownloaderBuilder=\
-org.springframework.cloud.contract.stubrunner.provider.moco.ClasspathStubProvider
+com.example.CustomStubDownloaderBuilder
repositoryRoot property or workOffline flag then Aether based
+ that will download stubs from a remote repo will be picked. If you don’t provide these
+ values then the ClasspathStubProvider will be picked that will scan the classpath.
+ If you provide more than one, then the first one on the list will be picked.