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.spring-doc.cn

Prerequisites

To use consumer-driven contracts with the contracts held in an external repository, you need to set up a git repository that:spring-doc.cn

  • Contains all the contract definitions for each producer.spring-doc.cn

  • Can package the contract definitions in a JAR.spring-doc.cn

  • For each contract producer, contains a way (for example, pom.xml) to install stubs locally through the Spring Cloud Contract Plugin (SCC Plugin).spring-doc.cn

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.spring-doc.cn

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.spring-doc.cn

At a high level, the flow is as follows:spring-doc.cn

  1. The consumer works with the contract definitions from the separate repository.spring-doc.cn

  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.spring-doc.cn

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

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

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

  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.spring-doc.cn

  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.spring-doc.cn

Consumer Flow

The consumer:spring-doc.cn

  1. Writes a test that would send a request to the producer.spring-doc.cn

    The test fails due to no server being present.spring-doc.cn

  2. Clones the repository that holds the contract definitions.spring-doc.cn

  3. Sets up the requirements as contracts under the folder, with the consumer name as a subfolder of the producer.spring-doc.cn

    For example, for a producer named producer and a consumer named consumer, the contracts would be stored under src/main/resources/contracts/producer/consumer/)spring-doc.cn

  4. Once the contracts are defined, installs the producer stubs to local storage, as the following example shows:spring-doc.cn

    $ cd src/main/resource/contracts/producer
    $ ./mvnw clean install
  5. Sets up Spring Cloud Contract (SCC) Stub Runner in the consumer tests, to:spring-doc.cn

    • Fetch the producer stubs from local storage.spring-doc.cn

    • Work in the stubs-per-consumer mode (this enables consumer driven contracts mode).spring-doc.cn

      The SCC Stub Runner:spring-doc.cn

    • Fetches the producer stubs.spring-doc.cn

    • Runs an in-memory HTTP server stub with the producer stubs. Now your test communicates with the HTTP server stub, and your tests pass.spring-doc.cn

    • Creates a pull request to the repository with contract definitions, with the new contracts for the producer.spring-doc.cn

    • Branches your consumer code, until the producer team has merged their code.spring-doc.cn

The following UML diagram shows the consumer flow:spring-doc.cn

flow-overview-consumer-cdc-external-consumer

Producer Flow

The producer:spring-doc.cn

  1. Takes over the pull request to the repository with contract definitions. You can do it from the command line, as followsspring-doc.cn

    $ 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 followsspring-doc.cn

    $ ./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:spring-doc.cn

    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:spring-doc.cn

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

  6. Merges the pull request to the repository with contract definitions, as follows:spring-doc.cn

    $ 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.spring-doc.cn

  7. Switches to working remotely.spring-doc.cn

  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:spring-doc.cn

    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.spring-doc.cn

  10. The CI system:spring-doc.cn

The following UML diagram shows the producer process:spring-doc.cn

flow-overview-consumer-cdc-external-producer