This section provides answers to some common “how do I do that…​?” questions that often arise when using Spring Cloud Contract. Its coverage is not exhaustive, but it does cover quite a lot.

If you have a specific problem that we do not cover here, you might want to check out stackoverflow.com to see if someone has already provided an answer. Stack Overflow is also a great place to ask new questions (please use the spring-cloud-contract tag).

We are also more than happy to extend this section. If you want to add a “how-to”, send us a pull request.

1. Why use Spring Cloud Contract?

Spring Cloud Contract works great in a polyglot environment. This project has a lot of really interesting features. Quite a few of these features definitely make Spring Cloud Contract Verifier stand out on the market of Consumer Driven Contract (CDC) tooling. The most interesting features include the following:

  • Ability to do CDC with messaging.

  • Clear and easy to use, statically typed DSL.

  • Ability to copy-paste your current JSON file to the contract and edit only its elements.

  • Automatic generation of tests from the defined contract.

  • Stub Runner functionality: The stubs are automatically downloaded at runtime from Nexus or Artifactory.

  • Spring Cloud integration: No discovery service is needed for integration tests.

  • Ability to add support for any language & framework through Docker.

2. How Can I Write Contracts in a Language Other than Groovy?

You can write a contract in YAML. See this section for more information.

We are working on allowing more ways of describing the contracts. You can check the github-issues for more information.

3. How Can I Provide Dynamic Values to a Contract?

One of the biggest challenges related to stubs is their reusability. Only if they can be widely used can they serve their purpose. The hard-coded values (such as dates and IDs) of request and response elements generally make that difficult. Consider the following JSON request:

{
    "time" : "2016-10-10 20:10:15",
    "id" : "9febab1c-6f36-4a0b-88d6-3b6a6d81cd4a",
    "body" : "foo"
}

Now consider the following JSON response:

{
    "time" : "2016-10-10 21:10:15",
    "id" : "c4231e1f-3ca9-48d3-b7e7-567d55f0d051",
    "body" : "bar"
}

Imagine the pain required to set the proper value of the time field (assume that this content is generated by the database) by changing the clock in the system or by providing stub implementations of data providers. The same is related to the id field. You could create a stubbed implementation of UUID generator, but doing so makes little sense.

So, as a consumer, you want to send a request that matches any form of a time or any UUID. That way, your system works as usual, generating data without you having to stub out anything. Assume that, in case of the aforementioned JSON, the most important part is the body field. You can focus on that and provide matching for other fields. In other words, you would like the stub to work as follows:

{
    "time" : "SOMETHING THAT MATCHES TIME",
    "id" : "SOMETHING THAT MATCHES UUID",
    "body" : "foo"
}

As far as the response goes, as a consumer, you need a concrete value on which you can operate. Consequently, the following JSON is valid:

{
    "time" : "2016-10-10 21:10:15",
    "id" : "c4231e1f-3ca9-48d3-b7e7-567d55f0d051",
    "body" : "bar"
}

In the previous sections, we generated tests from contracts. So, from the producer’s side, the situation looks much different. We parse the provided contract, and, in the test, we want to send a real request to your endpoints. So, for the case of a producer for the request, we cannot have any sort of matching. We need concrete values on which the producer’s backend can work. Consequently, the following JSON would be valid:

{
    "time" : "2016-10-10 20:10:15",
    "id" : "9febab1c-6f36-4a0b-88d6-3b6a6d81cd4a",
    "body" : "foo"
}

On the other hand, from the point of view of the validity of the contract, the response does not necessarily have to contain concrete values for time or id. Suppose you generate those on the producer side. Again, you have to do a lot of stubbing to ensure that you always return the same values. That is why, from the producer’s side, you might want the following response:

{
    "time" : "SOMETHING THAT MATCHES TIME",
    "id" : "SOMETHING THAT MATCHES UUID",
    "body" : "bar"
}

How can you then provide a matcher for the consumer and a concrete value for the producer (and the opposite at some other time)? Spring Cloud Contract lets you provide a dynamic value. That means that it can differ for both sides of the communication.

You can read more about this in the Contract DSL section.

Read the Groovy docs related to JSON to understand how to properly structure the request and response bodies.

4. How to Do Stubs versioning?

This section covers versioning of the stubs, which you can handle in a number of different ways:

4.1. API Versioning

What does versioning really mean? If you refer to the API version, there are different approaches:

  • Use hypermedia links and do not version your API by any means

  • Pass the version through headers and URLs

We do not try to answer the question of which approach is better. You should pick whatever suits your needs and lets you generate business value.

Assume that you do version your API. In that case, you should provide as many contracts with as many versions as you support. You can create a subfolder for every version or append it to the contract name — whatever suits you best.

4.2. JAR versioning

If, by versioning, you mean the version of the JAR that contains the stubs, then there are essentially two main approaches.

Assume that you do continuous delivery and deployment, which means that you generate a new version of the jar each time you go through the pipeline and that the jar can go to production at any time. For example, your jar version looks like the following (because it got built on the 20.10.2016 at 20:15:21) :

1.0.0.20161020-201521-RELEASE

In that case, your generated stub jar should look like the following:

1.0.0.20161020-201521-RELEASE-stubs.jar

In this case, you should, inside your application.yml or @AutoConfigureStubRunner when referencing stubs, provide the latest version of the stubs. You can do that by passing the + sign. the following example shows how to do so:

@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:8080"})

If the versioning, however, is fixed (for example, 1.0.4.RELEASE or 2.1.1), you have to set the concrete value of the jar version. The following example shows how to do so for version 2.1.1:

@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:2.1.1:stubs:8080"})

4.3. Development or Production Stubs

You can manipulate the classifier to run the tests against the current development version of the stubs of other services or the ones that were deployed to production. If you alter your build to deploy the stubs with the prod-stubs classifier once you reach production deployment, you can run tests in one case with development stubs and in another case with production stubs.

The following example works for tests that use the development version of the stubs:

@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:8080"})

The following example works for tests that use the production version of stubs:

@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:prod-stubs:8080"})

You can also pass those values also in properties from your deployment pipeline.

5. How Can I use a Common Repository with Contracts Instead of Storing Them with the Producer?

Another way of storing contracts, rather than having them with the producer, is to keep them in a common place. This situation can be related to security issues (where the consumers cannot clone the producer’s code). Also, if you keep contracts in a single place, then you, as a producer, know how many consumers you have and which consumer you may break with your local changes.

5.1. Repo Structure

Assume that we have a producer with coordinates of com.example:server and three consumers: client1, client2, and client3. Then, in the repository with common contracts, you could have the following setup (which you can check out here). The following listing shows such a structure:

├── com
│   └── example
│       └── server
│           ├── client1
│           │   └── expectation.groovy
│           ├── client2
│           │   └── expectation.groovy
│           ├── client3
│           │   └── expectation.groovy
│           └── pom.xml
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
    └── assembly
        └── contracts.xml

Under the slash-delimited groupid/artifact id folder (com/example/server), you have expectations of the three consumers (client1, 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 repository.

The following example shows a pom.xml file inside the server folder:

<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>server</artifactId>
    <version>0.0.1</version>

    <name>Server Stubs</name>
    <description>POM used to install locally stubs for consumer side</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.0.14</version>
        <relativePath/>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>17</java.version>
        <spring-cloud-contract.version>4.0.7-SNAPSHOT</spring-cloud-contract.version>
        <excludeBuildFolders>true</excludeBuildFolders>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-contract-maven-plugin</artifactId>
                <version>${spring-cloud-contract.version}</version>
                <extensions>true</extensions>
                <configuration>
                    <!-- By default it would search under src/test/resources/ -->
                    <contractsDirectory>${project.basedir}</contractsDirectory>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>spring-commercial-snapshot</id>
            <name>Spring Commercial Snapshot Repository</name>
            <url>https://repo.spring.vmware.com/artifactory/spring-commercial-snapshot-local</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
            <releases>
                <enabled>false</enabled>
            </releases>
        </repository>
        <repository>
            <id>spring-commercial-release</id>
            <name>Spring Commercial Release Repository</name>
            <url>https://repo.spring.vmware.com/artifactory/spring-commercial</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-commercial-snapshot</id>
            <name>Spring Commercial Snapshot Repository</name>
            <url>https://repo.spring.vmware.com/artifactory/spring-commercial-snapshot-local</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
            <releases>
                <enabled>false</enabled>
            </releases>
        </pluginRepository>
        <pluginRepository>
            <id>spring-commercial-release</id>
            <name>Spring Commercial Release Repository</name>
            <url>https://repo.spring.vmware.com/artifactory/spring-commercial</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>

</project>

There are no dependencies other than the Spring Cloud Contract Maven Plugin. Those pom.xml files are necessary for the consumer side to run mvn clean install -DskipTests to locally install the stubs of the producer project.

The pom.xml file in the root folder can look like the following:

<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example.standalone</groupId>
    <artifactId>contracts</artifactId>
    <version>0.0.1</version>

    <name>Contracts</name>
    <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>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <executions>
                    <execution>
                        <id>contracts</id>
                        <phase>prepare-package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                        <configuration>
                            <attach>true</attach>
                            <descriptor>${basedir}/src/assembly/contracts.xml</descriptor>
                            <!-- If you want an explicit classifier remove the following line -->
                            <appendAssemblyId>false</appendAssemblyId>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

It uses the assembly plugin to build the JAR with all the contracts. The following example shows such a setup:

<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 https://maven.apache.org/xsd/assembly-1.1.3.xsd">
    <id>project</id>
    <formats>
        <format>jar</format>
    </formats>
    <includeBaseDirectory>false</includeBaseDirectory>
    <fileSets>
        <fileSet>
            <directory>${project.basedir}</directory>
            <outputDirectory>/</outputDirectory>
            <useDefaultExcludes>true</useDefaultExcludes>
            <excludes>
                <exclude>**/${project.build.directory}/**</exclude>
                <exclude>mvnw</exclude>
                <exclude>mvnw.cmd</exclude>
                <exclude>.mvn/**</exclude>
                <exclude>src/**</exclude>
            </excludes>
        </fileSet>
    </fileSets>
</assembly>

5.2. Workflow

The workflow assumes that Spring Cloud Contract is set up both on the consumer and on the producer side. There is also the proper plugin setup in the common repository with contracts. The CI jobs are set for a common repository to build an artifact of all contracts and upload it to Nexus or Artifactory. The following image shows the UML for this workflow:

how to common repo

5.3. Consumer

When the consumer wants to work on the contracts offline, instead of cloning the producer code, the consumer team clones the common repository, goes to the required producer’s folder (for example, com/example/server) and runs mvn clean install -DskipTests to locally install the stubs converted from the contracts.

You need to have Maven installed locally.

5.4. Producer

As a producer, you can alter the Spring Cloud Contract Verifier to provide the URL and the dependency of the JAR that contains the contracts, as follows:

<plugin>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-contract-maven-plugin</artifactId>
    <configuration>
        <contractsMode>REMOTE</contractsMode>
        <contractsRepositoryUrl>
            https://link/to/your/nexus/or/artifactory/or/sth
        </contractsRepositoryUrl>
        <contractDependency>
            <groupId>com.example.standalone</groupId>
            <artifactId>contracts</artifactId>
        </contractDependency>
    </configuration>
</plugin>

With this setup, the JAR with a groupid of com.example.standalone and an artifactid of contracts is downloaded from link/to/your/nexus/or/artifactory/or/sth. It is then unpacked in a local temporary folder, and the contracts present in com/example/server are picked as the ones used to generate the tests and the stubs. Due to this convention, the producer team can know which consumer teams are broken when some incompatible changes are made.

The rest of the flow looks the same.

5.5. How Can I Define Messaging Contracts per Topic Rather than per Producer?

To avoid messaging contracts duplication in the common repository, when a few producers write messages to one topic, we could create a structure in which the REST contracts are placed in a folder per producer and messaging contracts are placed in the folder per topic.

5.5.1. For Maven Projects

To make it possible to work on the producer side, we should specify an inclusion pattern for filtering common repository jar files by messaging topics we are interested in. The includedFiles property of the Maven Spring Cloud Contract plugin lets us do so. Also, contractsPath need to be specified, since the default path would be the common repository groupid/artifactid. The following example shows a Maven plugin for Spring Cloud Contract:

<plugin>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-contract-maven-plugin</artifactId>
   <version>${spring-cloud-contract.version}</version>
   <configuration>
      <contractsMode>REMOTE</contractsMode>
      <contractsRepositoryUrl>https://link/to/your/nexus/or/artifactory/or/sth</contractsRepositoryUrl>
      <contractDependency>
         <groupId>com.example</groupId>
         <artifactId>common-repo-with-contracts</artifactId>
         <version>+</version>
      </contractDependency>
      <contractsPath>/</contractsPath>
      <baseClassMappings>
         <baseClassMapping>
            <contractPackageRegex>.*messaging.*</contractPackageRegex>
            <baseClassFQN>com.example.services.MessagingBase</baseClassFQN>
         </baseClassMapping>
         <baseClassMapping>
            <contractPackageRegex>.*rest.*</contractPackageRegex>
            <baseClassFQN>com.example.services.TestBase</baseClassFQN>
         </baseClassMapping>
      </baseClassMappings>
      <includedFiles>
         <includedFile>**/${project.artifactId}/**</includedFile>
         <includedFile>**/${first-topic}/**</includedFile>
         <includedFile>**/${second-topic}/**</includedFile>
      </includedFiles>
   </configuration>
</plugin>
Many of the values in the preceding Maven plugin can be changed. We included it for illustration purposes rather than trying to provide a “typical” example.

5.5.2. For Gradle Projects

To work with a Gradle project:

  1. Add a custom configuration for the common repository dependency, as follows:

    ext {
        contractsGroupId = "com.example"
        contractsArtifactId = "common-repo"
        contractsVersion = "1.2.3"
    }
    
    configurations {
        contracts {
            transitive = false
        }
    }
    
  2. Add the common repository dependency to your classpath, as follows:

    dependencies {
        contracts "${contractsGroupId}:${contractsArtifactId}:${contractsVersion}"
        testCompile "${contractsGroupId}:${contractsArtifactId}:${contractsVersion}"
    }
    
  3. Download the dependency to an appropriate folder, as follows:

    task getContracts(type: Copy) {
        from configurations.contracts
        into new File(project.buildDir, "downloadedContracts")
    }
    
  4. Unzip the JAR, as follows:

    task unzipContracts(type: Copy) {
        def zipFile = new File(project.buildDir, "downloadedContracts/${contractsArtifactId}-${contractsVersion}.jar")
        def outputDir = file("${buildDir}/unpackedContracts")
    
        from zipTree(zipFile)
        into outputDir
    }
    
  5. Cleanup unused contracts, as follows:

    task deleteUnwantedContracts(type: Delete) {
        delete fileTree(dir: "${buildDir}/unpackedContracts",
            include: "**/*",
            excludes: [
                "**/${project.name}/**"",
                "**/${first-topic}/**",
                "**/${second-topic}/**"])
    }
    
  6. Create task dependencies, as follows:

    unzipContracts.dependsOn("getContracts")
    deleteUnwantedContracts.dependsOn("unzipContracts")
    build.dependsOn("deleteUnwantedContracts")
    
  7. Configure the plugin by specifying the directory that contains the contracts, by setting the contractsDslDir property, as follows:

    contracts {
        contractsDslDir = new File("${buildDir}/unpackedContracts")
    }
    

6. How Can I Use Git as the Storage for Contracts and Stubs?

In the polyglot world, there are languages that do not use binary storage, as Artifactory and Nexus do. Starting from Spring Cloud Contract version 2.0.0, we provide mechanisms to store contracts and stubs in a SCM (Source Control Management) repository. Currently, the only supported SCM is Git.

The repository would have to have the following setup (which you can checkout from here):

.
└── META-INF
    └── com.example
        └── beer-api-producer-git
            └── 0.0.1-SNAPSHOT
                ├── contracts
                │   └── beer-api-consumer
                │       ├── messaging
                │       │   ├── shouldSendAcceptedVerification.groovy
                │       │   └── shouldSendRejectedVerification.groovy
                │       └── rest
                │           ├── shouldGrantABeerIfOldEnough.groovy
                │           └── shouldRejectABeerIfTooYoung.groovy
                └── mappings
                    └── beer-api-consumer
                        └── rest
                            ├── shouldGrantABeerIfOldEnough.json
                            └── shouldRejectABeerIfTooYoung.json

Under the META-INF folder:

  • We group applications by groupId (such as com.example).

  • Each application is represented by its artifactId (for example, beer-api-producer-git).

  • Next, each application is organized by its version (such as 0.0.1-SNAPSHOT). Starting from Spring Cloud Contract version 2.1.0, you can specify the versions as follows (assuming that your versions follow semantic versioning):

    • + or latest: To find the latest version of your stubs (assuming that the snapshots are always the latest artifact for a given revision number). That means:

      • If you have 1.0.0.RELEASE, 2.0.0.BUILD-SNAPSHOT, and 2.0.0.RELEASE, we assume that the latest is 2.0.0.BUILD-SNAPSHOT.

      • If you have 1.0.0.RELEASE and 2.0.0.RELEASE, we assume that the latest is 2.0.0.RELEASE.

      • If you have a version called latest or +, we will pick that folder.

    • release: To find the latest release version of your stubs. That means:

      • If you have 1.0.0.RELEASE, 2.0.0.BUILD-SNAPSHOT, and 2.0.0.RELEASE we assume that the latest is 2.0.0.RELEASE.

      • If you have a version called release, we pick that folder.

Finally, there are two folders:

  • contracts: The good practice is to store the contracts required by each consumer in the folder with the consumer name (such as beer-api-consumer). That way, you can use the stubs-per-consumer feature. Further directory structure is arbitrary.

  • mappings: The Maven or Gradle Spring Cloud Contract plugins push the stub server mappings in this folder. On the consumer side, Stub Runner scans this folder to start stub servers with stub definitions. The folder structure is a copy of the one created in the contracts subfolder.

6.1. Protocol Convention

To control the type and location of the source of contracts (whether binary storage or an SCM repository), you can use the protocol in the URL of the repository. Spring Cloud Contract iterates over registered protocol resolvers and tries to fetch the contracts (by using a plugin) or stubs (from Stub Runner).

For the SCM functionality, currently, we support the Git repository. To use it, in the property where the repository URL needs to be placed, you have to prefix the connection URL with git://. The following listing shows some examples:

git://file:///foo/bar
git://https://github.com/spring-cloud-samples/spring-cloud-contract-nodejs-contracts-git.git
git://[email protected]:spring-cloud-samples/spring-cloud-contract-nodejs-contracts-git.git

6.2. Producer

For the producer, to use the SCM (Source Control Management) approach, we can reuse the same mechanism we use for external contracts. We route Spring Cloud Contract to use the SCM implementation from the URL that starts with the git:// protocol.

You have to manually add the pushStubsToScm goal in Maven or use (bind) the pushStubsToScm task in Gradle. We do not push stubs to the origin of your git repository.

The following listing includes the relevant parts both Maven and Gradle build files:

Maven
<plugin>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-contract-maven-plugin</artifactId>
    <version>${spring-cloud-contract.version}</version>
    <extensions>true</extensions>
    <configuration>
        <!-- Base class mappings etc. -->

        <!-- We want to pick contracts from a Git repository -->
        <contractsRepositoryUrl>git://https://github.com/spring-cloud-samples/spring-cloud-contract-nodejs-contracts-git.git</contractsRepositoryUrl>

        <!-- We reuse the contract dependency section to set up the path
        to the folder that contains the contract definitions. In our case the
        path will be /groupId/artifactId/version/contracts -->
        <contractDependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>${project.artifactId}</artifactId>
            <version>${project.version}</version>
        </contractDependency>

        <!-- The contracts mode can't be classpath -->
        <contractsMode>REMOTE</contractsMode>
    </configuration>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <!-- By default we will not push the stubs back to SCM,
                you have to explicitly add it as a goal -->
                <goal>pushStubsToScm</goal>
            </goals>
        </execution>
    </executions>
</plugin>
Gradle
contracts {
    // We want to pick contracts from a Git repository
    contractDependency {
        stringNotation = "${project.group}:${project.name}:${project.version}"
    }
    /*
    We reuse the contract dependency section to set up the path
    to the folder that contains the contract definitions. In our case the
    path will be /groupId/artifactId/version/contracts
     */
    contractRepository {
        repositoryUrl = "git://https://github.com/spring-cloud-samples/spring-cloud-contract-nodejs-contracts-git.git"
    }
    // The mode can't be classpath
    contractsMode = "REMOTE"
    // Base class mappings etc.
}

/*
In this scenario we want to publish stubs to SCM whenever
the `publish` task is invoked
*/
publish.dependsOn("publishStubsToScm")

You can also further customize the publishStubsToScm gradle task. In the following example, the task is customized to pick contracts from a local git repository:

gradle
publishStubsToScm {
    // We want to modify the default set up of the plugin when publish stubs to scm is called
    // We want to pick contracts from a Git repository
    contractDependency {
        stringNotation = "${project.group}:${project.name}:${project.version}"
    }
    /*
    We reuse the contract dependency section to set up the path
    to the folder that contains the contract definitions. In our case the
    path will be /groupId/artifactId/version/contracts
     */
    contractRepository {
        repositoryUrl = "git://file://${new File(project.rootDir, "../target")}/contract_empty_git/"
    }
    // We set the contracts mode to `LOCAL`
    contractsMode = "LOCAL"
    }
IMPORTANT

Starting with the 2.3.0.RELEASE, the customize{} closure previously used for the publishStubsToScm customization is no longer available. The settings should be applied directly within the publishStubsToScm closure, as in the preceding example.

With such a setup:

  • A git project is cloned to a temporary directory

  • The SCM stub downloader goes to the META-INF/groupId/artifactId/version/contracts folder to find contracts. For example, for com.example:foo:1.0.0, the path would be META-INF/com.example/foo/1.0.0/contracts.

  • Tests are generated from the contracts.

  • Stubs are created from the contracts.

  • Once the tests pass, the stubs are committed in the cloned repository.

  • Finally, a push is sent to that repo’s origin.

6.3. Producer with Contracts Stored Locally

Another option to use the SCM as the destination for stubs and contracts is to store the contracts locally, with the producer, and only push the contracts and the stubs to SCM. The following listing shows the setup required to achieve this with Maven and Gradle:

Maven
<plugin>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-contract-maven-plugin</artifactId>
    <version>${spring-cloud-contract.version}</version>
    <extensions>true</extensions>
    <!-- In the default configuration, we want to use the contracts stored locally -->
    <configuration>
        <baseClassMappings>
            <baseClassMapping>
                <contractPackageRegex>.*messaging.*</contractPackageRegex>
                <baseClassFQN>com.example.BeerMessagingBase</baseClassFQN>
            </baseClassMapping>
            <baseClassMapping>
                <contractPackageRegex>.*rest.*</contractPackageRegex>
                <baseClassFQN>com.example.BeerRestBase</baseClassFQN>
            </baseClassMapping>
        </baseClassMappings>
        <basePackageForTests>com.example</basePackageForTests>
    </configuration>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <!-- By default we will not push the stubs back to SCM,
                you have to explicitly add it as a goal -->
                <goal>pushStubsToScm</goal>
            </goals>
            <configuration>
                <!-- We want to pick contracts from a Git repository -->
                <contractsRepositoryUrl>git://file://${env.ROOT}/target/contract_empty_git/
                </contractsRepositoryUrl>
                <!-- Example of URL via git protocol -->
                <!--<contractsRepositoryUrl>git://[email protected]:spring-cloud-samples/spring-cloud-contract-samples.git</contractsRepositoryUrl>-->
                <!-- Example of URL via http protocol -->
                <!--<contractsRepositoryUrl>git://https://github.com/spring-cloud-samples/spring-cloud-contract-samples.git</contractsRepositoryUrl>-->
                <!-- We reuse the contract dependency section to set up the path
                to the folder that contains the contract definitions. In our case the
                path will be /groupId/artifactId/version/contracts -->
                <contractDependency>
                    <groupId>${project.groupId}</groupId>
                    <artifactId>${project.artifactId}</artifactId>
                    <version>${project.version}</version>
                </contractDependency>
                <!-- The mode can't be classpath -->
                <contractsMode>LOCAL</contractsMode>
            </configuration>
        </execution>
    </executions>
</plugin>
Gradle
contracts {
    contractsDslDir = file("src/test/resources/contracts")
    testFramework = "JUNIT5"
        // Base package for generated tests
    basePackageForTests = "com.example"
    baseClassMappings {
        baseClassMapping(".*messaging.*", "com.example.BeerMessagingBase")
        baseClassMapping(".*rest.*", "com.example.BeerRestBase")
    }

    /*
    In this scenario we want to publish stubs to SCM whenever
    the `publish` task is executed
    */
    publishStubsToScm {

        // We want to pick contracts from a Git repository
        contractDependency {
            stringNotation = "${project.group}:${project.name}:${project.version}"
        }

        contractRepository {
            /*
            We reuse the contract dependency section to set up the path
            to the folder that contains the contract definitions. In our case the
            path will be /groupId/artifactId/version/contracts
             */
            repositoryUrl = "git://file://${new File(project.rootDir, "../target")}/contract_empty_git/"
        }
    }
}

With such a setup:

  • Contracts from the default src/test/resources/contracts directory are picked.

  • Tests are generated from the contracts.

  • Stubs are created from the contracts.

  • Once the tests pass:

    • The git project is cloned to a temporary directory.

    • The stubs and contracts are committed in the cloned repository.

  • Finally, a push is done to that repository’s origin.

6.4. Keeping Contracts with the Producer and Stubs in an External Repository

You can also keep the contracts in the producer repository but keep the stubs in an external git repository. This is most useful when you want to use the base consumer-producer collaboration flow but cannot use an artifact repository to store the stubs.

To do so, use the usual producer setup and then add the pushStubsToScm goal and set contractsRepositoryUrl to the repository where you want to keep the stubs.

6.5. Consumer

On the consumer side, when passing the repositoryRoot parameter, either from the @AutoConfigureStubRunner annotation, the JUnit 4 rule, JUnit 5 extension, or properties, you can pass the URL of the SCM repository, prefixed with the git:// protocol. The following example shows how to do so:

@AutoConfigureStubRunner(
    stubsMode="REMOTE",
    repositoryRoot="git://https://github.com/spring-cloud-samples/spring-cloud-contract-nodejs-contracts-git.git",
    ids="com.example:bookstore:0.0.1.RELEASE"
)

With such a setup:

  • The git project is cloned to a temporary directory.

  • The SCM stub downloader goes to the META-INF/groupId/artifactId/version/ folder to find stub definitions and contracts. For example, for com.example:foo:1.0.0, the path would be META-INF/com.example/foo/1.0.0/.

  • Stub servers are started and fed with mappings.

  • Messaging definitions are read and used in the messaging tests.

7. How Can I Debug the Request/Response Being Sent by the Generated Tests Client?

The generated tests all boil down to RestAssured in some form or fashion. RestAssured relies on the Apache HttpClient. HttpClient has a facility called wire logging, which logs the entire request and response to HttpClient. Spring Boot has a logging common application property for doing this sort of thing. To use it, add it to your application properties, as follows:

logging.level.org.apache.http.wire=DEBUG

8. How Can I Debug the Mapping, Request, or Response Being Sent by WireMock?

Starting from version 1.2.0, we set WireMock logging to info and set the WireMock notifier to being verbose. Now you can exactly know what request was received by the WireMock server and which matching response definition was picked.

To turn off this feature, set WireMock logging to ERROR, as follows:

logging.level.com.github.tomakehurst.wiremock=ERROR

9. How Can I See What Got Registered in the HTTP Server Stub?

You can use the mappingsOutputFolder property on @AutoConfigureStubRunner, StubRunnerRule, or StubRunnerExtension to dump all mappings for each artifact ID. Also, the port at which the given stub server was started is attached.

10. How Can I Reference Text from File?

In version 1.2.0, we added this ability. You can call a file(…​) method in the DSL and provide a path relative to where the contract lies. If you use YAML, you can use the bodyFromFile property.

11. How Can I Generate Pact, YAML, or X files from Spring Cloud Contract Contracts?

Spring Cloud Contract comes with a ToFileContractsTransformer class that lets you dump contracts as files for the given ContractConverter. It contains a static void main method that lets you run the transformer as an executable. It takes the following arguments:

  • argument 1 : FQN: Fully qualified name of the ContractConverter (for example, PactContractConverter). REQUIRED.

  • argument 2 : path: Path where the dumped files should be stored. OPTIONAL — defaults to target/converted-contracts.

  • argument 3 : path: Path were the contracts should be searched for. OPTIONAL — defaults to src/test/resources/contracts.

After calling the transformer, the Spring Cloud Contract files are processed and, depending on the provided FQN of the ContractTransformer, the contracts are transformed to the required format and dumped to the provided folder.

The following example shows how to configure Pact integration for both Maven and Gradle:

Maven
<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>exec-maven-plugin</artifactId>
    <version>1.6.0</version>
    <executions>
        <execution>
            <id>convert-dsl-to-pact</id>
            <phase>process-test-classes</phase>
            <configuration>
                <classpathScope>test</classpathScope>
                <mainClass>
                    org.springframework.cloud.contract.verifier.util.ToFileContractsTransformer
                </mainClass>
                <arguments>
                    <argument>
                        org.springframework.cloud.contract.verifier.spec.pact.PactContractConverter
                    </argument>
                    <argument>${project.basedir}/target/pacts</argument>
                    <argument>
                        ${project.basedir}/src/test/resources/contracts
                    </argument>
                </arguments>
            </configuration>
            <goals>
                <goal>java</goal>
            </goals>
        </execution>
    </executions>
</plugin>
Gradle
task convertContracts(type: JavaExec) {
    main = "org.springframework.cloud.contract.verifier.util.ToFileContractsTransformer"
    classpath = sourceSets.test.compileClasspath
    args("org.springframework.cloud.contract.verifier.spec.pact.PactContractConverter",
            "${project.rootDir}/build/pacts", "${project.rootDir}/src/test/resources/contracts")
}

test.dependsOn("convertContracts")

12. How Can I Work with Transitive Dependencies?

The Spring Cloud Contract plugins add the tasks that create the stubs jar for you. One problem that arises is that, when reusing the stubs, you can mistakenly import all of that stub’s dependencies. When building a Maven artifact, even though you have a couple of different jars, all of them share one pom.xml file, as the following listing shows:

├── producer-0.0.1.BUILD-20160903.075506-1-stubs.jar
├── producer-0.0.1.BUILD-20160903.075506-1-stubs.jar.sha1
├── producer-0.0.1.BUILD-20160903.075655-2-stubs.jar
├── producer-0.0.1.BUILD-20160903.075655-2-stubs.jar.sha1
├── producer-0.0.1.BUILD-SNAPSHOT.jar
├── producer-0.0.1.BUILD-SNAPSHOT.pom
├── producer-0.0.1.BUILD-SNAPSHOT-stubs.jar
├── ...
└── ...

There are three possibilities of working with those dependencies so as not to have any issues with transitive dependencies:

  • Mark all application dependencies as optional

  • Create a separate artifactid for the stubs

  • Exclude dependencies on the consumer side

12.1. How Can I Mark All Application Dependencies as Optional?

If, in the producer application, you mark all of your dependencies as optional, when you include the producer stubs in another application (or when that dependency gets downloaded by Stub Runner), then, since all of the dependencies are optional, they do not get downloaded.

12.2. How can I Create a Separate artifactid for the Stubs?

If you create a separate artifactid, you can set it up in whatever way you wish. For example, you might decide to have no dependencies at all.

12.3. How can I Exclude Dependencies on the Consumer Side?

As a consumer, if you add the stub dependency to your classpath, you can explicitly exclude the unwanted dependencies.

13. How Can I Generate Spring REST Docs Snippets from the Contracts?

When you want to include the requests and responses of your API by using Spring REST Docs, you only need to make some minor changes to your setup if you are using MockMvc and RestAssuredMockMvc. To do so, include the following dependencies (if you have not already done so):

maven
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-contract-verifier</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework.restdocs</groupId>
    <artifactId>spring-restdocs-mockmvc</artifactId>
    <optional>true</optional>
</dependency>
gradle
testImplementation 'org.springframework.cloud:spring-cloud-starter-contract-verifier'
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'

Next, you need to make some changes to your base class. The following examples use WebAppContext and the standalone option with RestAssured:

WebAppContext
package com.example.fraud;

import io.restassured.module.mockmvc.RestAssuredMockMvc;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.extension.ExtendWith;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.restdocs.RestDocumentationContextProvider;
import org.springframework.restdocs.RestDocumentationExtension;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;

@ExtendWith(RestDocumentationExtension.class)
@SpringBootTest(classes = Application.class)
public abstract class FraudBaseWithWebAppSetup {

    @Autowired
    private WebApplicationContext context;

    @BeforeEach
    public void setup(TestInfo info, RestDocumentationContextProvider restDocumentation) {
        RestAssuredMockMvc.mockMvc(MockMvcBuilders.webAppContextSetup(this.context)
                .apply(documentationConfiguration(restDocumentation))
                .alwaysDo(document(
                        getClass().getSimpleName() + "_" + info.getDisplayName()))
                .build());
    }

    protected void assertThatRejectionReasonIsNull(Object rejectionReason) {
        assert rejectionReason == null;
    }

}
Standalone
package com.example.fraud;

import io.restassured.module.mockmvc.RestAssuredMockMvc;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.extension.ExtendWith;

import org.springframework.restdocs.RestDocumentationContextProvider;
import org.springframework.restdocs.RestDocumentationExtension;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;

@ExtendWith(RestDocumentationExtension.class)
public abstract class FraudBaseWithStandaloneSetup {

    @BeforeEach
    public void setup(TestInfo info, RestDocumentationContextProvider restDocumentation) {
        RestAssuredMockMvc.standaloneSetup(MockMvcBuilders
                .standaloneSetup(new FraudDetectionController())
                .apply(documentationConfiguration(restDocumentation))
                .alwaysDo(document(
                        getClass().getSimpleName() + "_" + info.getDisplayName())));
    }

}
You need not specify the output directory for the generated snippets (since version 1.2.0.RELEASE of Spring REST Docs).

14. How Can I Use Stubs from a Location

If you want to fetch contracts or stubs from a given location without cloning a repository or fetching a JAR, use the stubs:// protocol when providing the repository root argument for Stub Runner or the Spring Cloud Contract plugin. You can read more about this in this section of the documentation.

15. How Can I Generate Stubs at Runtime

If you want to generate stubs at runtime for contracts, switch the generateStubs property in the @AutoConfigureStubRunner annotation, or call the withGenerateStubs(true) method on the JUnit Rule or Extension. You can read more about this in this section of the documentation.

16. How Can I Make The Build Pass if There Are No Contracts or Stubs

If you want Stub Runner not to fail if no stubs were found, switch the generateStubs property in the @AutoConfigureStubRunner annotation or call the withFailOnNoStubs(false) method on the JUnit Rule or Extension. You can read more about this in this section of the documentation.

If you want the plugins not to fail the build when no contracts were found, you can set the failOnNoStubs flag in Maven or call the contractRepository { failOnNoStubs(false) } closure in Gradle.

17. How Can I Mark that a Contract Is in Progress

If a contract is in progress, it means that the, on the producer side, tests are not generated, but the stub is generated. You can read more about this in this section of the documentation.

In a CI build, before going to production, you would like to ensure that no in-progress contracts are on the classpath, because they may lead to false positives. For this reason, by default, in the Spring Cloud Contract plugin, we set the value of failOnInProgress to true. If you want to allow such contracts when tests are to be generated, set the flag to false.