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:

  1. The consumer works with the contract definitions from the separate repository.

  2. 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.

  3. The producer takes over the pull request to the separate repository with contract definitions and installs the JAR with all contracts locally.

  4. The producer generates tests from the locally stored JAR and writes the missing implementation to make the tests pass.

  5. Once the producer’s work is done, the pull request to the repository that holds the contract definitions is merged.

  6. 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.

  7. 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:

  1. Writes a test that would send a request to the producer.

    The test fails due to no server being present.

  2. Clones the repository that holds the contract definitions.

  3. 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 named consumer, the contracts would be stored under src/main/resources/contracts/producer/consumer/)

  4. 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
  5. 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:

flow-overview-consumer-cdc-external-consumer

Producer Flow

The producer:

  1. 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
  2. Installs the contract definitions, as follows

    $ ./mvnw clean install
  3. 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
    }
  4. Runs the build to generate tests and stubs, as follows:

    Maven
    ./mvnw clean install
    Gradle
    ./gradlew clean build
  5. Writes the missing implementation, to make the tests pass.

  6. 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.

  7. Switches to working remotely.

  8. 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
    }
  9. Merges the producer code with the new implementation.

  10. 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:

flow-overview-consumer-cdc-external-producer