Consumer Driven Contracts with Contracts in an External Repository
In this flow, we perform Consumer Driven Contract testing. The contract definitions are stored in a separate repository.
Prerequisites
To use consumer-driven contracts with the contracts held in an external repository, you need to set up a git repository that:
-
Contains all the contract definitions for each producer.
-
Can package the contract definitions in a JAR.
-
For each contract producer, contains a way (for example,
pom.xml
) to install stubs locally through the Spring Cloud Contract Plugin (SCC Plugin).
For more information, see the How To section, where we describe how to set up such a repository. For an example of such a project, see this sample.
You also need consumer code that has Spring Cloud Contract Stub Runner set up. For an example of such a project, see this sample. You also need producer code that has Spring Cloud Contract set up, together with a plugin. For an example of such a project, see this sample. The stub storage is Nexus or Artifactory.
At a high level, the flow is as follows:
-
The consumer works with the contract definitions from the separate repository.
-
Once the consumer’s work is done, a branch with working code is created on the consumer side, and a pull request is made to the separate repository that holds the contract definitions.
-
The producer takes over the pull request to the separate repository with contract definitions and installs the JAR with all contracts locally.
-
The producer generates tests from the locally stored JAR and writes the missing implementation to make the tests pass.
-
Once the producer’s work is done, the pull request to the repository that holds the contract definitions is merged.
-
After the CI tool builds the repository with the contract definitions and the JAR with contract definitions gets uploaded to Nexus or Artifactory, the producer can merge its branch.
-
Finally, the consumer can switch to working online to fetch stubs of the producer from a remote location, and the branch can be merged to master.
Consumer Flow
The consumer:
-
Writes a test that would send a request to the producer.
The test fails due to no server being present.
-
Clones the repository that holds the contract definitions.
-
Sets up the requirements as contracts under the folder, with the consumer name as a subfolder of the producer.
For example, for a producer named
producer
and a consumer namedconsumer
, the contracts would be stored undersrc/main/resources/contracts/producer/consumer/
) -
Once the contracts are defined, installs the producer stubs to local storage, as the following example shows:
$ cd src/main/resource/contracts/producer $ ./mvnw clean install
-
Sets up Spring Cloud Contract (SCC) Stub Runner in the consumer tests, to:
-
Fetch the producer stubs from local storage.
-
Work in the stubs-per-consumer mode (this enables consumer driven contracts mode).
The SCC Stub Runner:
-
Fetches the producer stubs.
-
Runs an in-memory HTTP server stub with the producer stubs. Now your test communicates with the HTTP server stub, and your tests pass.
-
Creates a pull request to the repository with contract definitions, with the new contracts for the producer.
-
Branches your consumer code, until the producer team has merged their code.
-
The following UML diagram shows the consumer flow:
Producer Flow
The producer:
-
Takes over the pull request to the repository with contract definitions. You can do it from the command line, as follows
$ git checkout -b the_branch_with_pull_request master git pull https://github.com/user_id/project_name.git the_branch_with_pull_request
-
Installs the contract definitions, as follows
$ ./mvnw clean install
-
Sets up the plugin to fetch the contract definitions from a JAR instead of from
src/test/resources/contracts
, as follows:- Maven
-
<plugin> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-contract-maven-plugin</artifactId> <version>${spring-cloud-contract.version}</version> <extensions>true</extensions> <configuration> <!-- We want to use the JAR with contracts with the following coordinates --> <contractDependency> <groupId>com.example</groupId> <artifactId>beer-contracts</artifactId> </contractDependency> <!-- The JAR with contracts should be taken from Maven local --> <contractsMode>LOCAL</contractsMode> <!-- ... additional configuration --> </configuration> </plugin>
- Gradle
-
contracts { // We want to use the JAR with contracts with the following coordinates // group id `com.example`, artifact id `beer-contracts`, LATEST version and NO classifier contractDependency { stringNotation = 'com.example:beer-contracts:+:' } // The JAR with contracts should be taken from Maven local contractsMode = "LOCAL" // Additional configuration }
-
Runs the build to generate tests and stubs, as follows:
- Maven
-
./mvnw clean install
- Gradle
-
./gradlew clean build
-
Writes the missing implementation, to make the tests pass.
-
Merges the pull request to the repository with contract definitions, as follows:
$ git commit -am "Finished the implementation to make the contract tests pass" $ git checkout master $ git merge --no-ff the_branch_with_pull_request $ git push origin master
The CI system builds the project with the contract definitions and uploads the JAR with the contract definitions to Nexus or Artifactory.
-
Switches to working remotely.
-
Sets up the plugin so that the contract definitions are no longer taken from the local storage but from a remote location, as follows:
- Maven
-
<plugin> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-contract-maven-plugin</artifactId> <version>${spring-cloud-contract.version}</version> <extensions>true</extensions> <configuration> <!-- We want to use the JAR with contracts with the following coordinates --> <contractDependency> <groupId>com.example</groupId> <artifactId>beer-contracts</artifactId> </contractDependency> <!-- The JAR with contracts should be taken from a remote location --> <contractsMode>REMOTE</contractsMode> <!-- ... additional configuration --> </configuration> </plugin>
- Gradle
-
contracts { // We want to use the JAR with contracts with the following coordinates // group id `com.example`, artifact id `beer-contracts`, LATEST version and NO classifier contractDependency { stringNotation = 'com.example:beer-contracts:+:' } // The JAR with contracts should be taken from a remote location contractsMode = "REMOTE" // Additional configuration }
-
Merges the producer code with the new implementation.
-
The CI system:
-
Builds the project.
-
Generates tests, stubs, and the stub JAR.
-
Uploads the artifact with the application and the stubs to Nexus or Artifactory.
-
The following UML diagram shows the producer process: