使用 Spring Cloud Contract
1. 在 Nexus 或 Artifactory 中使用 Stub 进行提供商合同测试
您可以查看开发第一个基于 Spring Cloud Contract 的应用程序链接,以查看在 Nexus 或 Artifactory 流程中使用存根进行提供商 Contract 测试。
2. 在 Git 中使用存根进行提供商合同测试
在这个流程中,我们执行提供者契约测试(生产者不知道消费者如何使用他们的 API)。存根将上传到单独的存储库(它们不会上传到 Artifactory 或 Nexus)。
2.1. 先决条件
$ tree .
└── META-INF
└── folder.with.group.id.as.its.name
└── folder-with-artifact-id
└── folder-with-version
├── contractA.groovy
├── contractB.yml
└── contractC.groovy
2.2. 流程
该流程看起来与开发第一个基于 Spring Cloud Contract 的应用程序 中提供的流程完全相同。
但是Stub Storage
implementation 是一个 git 存储库。
您可以阅读有关设置 git 存储库和设置 consumer 和 producer 端的更多信息 在文档的 How To 页面中。
2.3. 消费者设置
为了从 git 存储库而不是 Nexus 或 Artifactory 获取存根,您需要
需要使用git
协议的repositoryRoot
属性。
以下示例显示了如何设置它:
@AutoConfigureStubRunner(
stubsMode = StubRunnerProperties.StubsMode.REMOTE,
repositoryRoot = "git://[email protected]:spring-cloud-samples/spring-cloud-contract-nodejs-contracts-git.git",
ids = "com.example:artifact-id:0.0.1")
@Rule
public StubRunnerRule rule = new StubRunnerRule()
.downloadStub("com.example","artifact-id", "0.0.1")
.repoRoot("git://[email protected]:spring-cloud-samples/spring-cloud-contract-nodejs-contracts-git.git")
.stubsMode(StubRunnerProperties.StubsMode.REMOTE);
@RegisterExtension
public StubRunnerExtension stubRunnerExtension = new StubRunnerExtension()
.downloadStub("com.example","artifact-id", "0.0.1")
.repoRoot("git://[email protected]:spring-cloud-samples/spring-cloud-contract-nodejs-contracts-git.git")
.stubsMode(StubRunnerProperties.StubsMode.REMOTE);
2.4. 设置 Producer
要将存根推送到 git 存储库而不是 Nexus 或 Artifactory,您需要
要使用git
protocol 的 URL 中。此外,您需要明确告知
用于在构建过程结束时推送存根的插件。以下示例显示
如何在 Maven 和 Gradle 中执行此作:
<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://git://[email protected]: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>
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://git://[email protected]: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 run
*/
publish.dependsOn("publishStubsToScm")
您可以在文档的 How To 部分阅读有关设置 git 存储库的更多信息。
3. 消费者驱动的合约,生产者端的合约
查看 Consumer Driven 分步指南 Contracts (CDC) 与 Contracts 在 Producer 端查看消费者驱动的 Contract with Contract on the producer side 流。
4. 具有外部存储库中的合同的消费者驱动合同
在这个流程中,我们执行 Consumer Driven Contract 测试。合同定义包括 存储在单独的存储库中。
4.1. 先决条件
要将使用者驱动的协定与外部存储库中保存的协定一起使用,您需要设置一个 git 存储库,该存储库:
-
包含每个生产者的所有合同定义。
-
可以将 Contract 定义打包到 JAR 中。
-
对于每个合同生成者,包含一个方式(例如
pom.xml
) 安装 stub 通过 Spring Cloud Contract Plugin(SCC 插件)在本地进行。
您还需要设置了 Spring Cloud Contract Stub Runner 的使用者代码。 有关此类项目的示例,请参阅此示例。 您还需要设置了 Spring Cloud Contract 的生产者代码以及插件。 有关此类项目的示例,请参阅此示例。 存根存储是 Nexus 或 Artifactory。
概括地说,流程如下:
-
使用者使用来自单独存储库的 Contract 定义。
-
一旦消费者的工作完成,就会在消费者上创建一个带有工作代码的分支 side 中,并向包含合同定义的单独存储库发出拉取请求。
-
创建者通过 Contract 接管对单独存储库的拉取请求 定义并在本地安装包含所有 Contract 的 JAR。
-
创建者从本地存储的 JAR 生成测试,并写入缺失的 implementation 使测试通过。
-
创建者的工作完成后,对包含该 Contract definitions 被合并。
-
在 CI 工具使用合约定义构建存储库并使用 JAR 合约定义上传到 Nexus 或 Artifactory,生产者可以合并其分支。
-
最后,消费者可以切换到在线工作,从 remote 位置,并且该分支可以合并到 master 中。
4.2. 消费者流程
消费者:
-
编写一个测试,该测试将向创建者发送请求。
由于没有服务器存在,测试失败。
-
克隆包含合同定义的存储库。
-
将需求设置为文件夹下的合同,并将使用者名称作为生产者的子文件夹。
例如,对于名为
producer
和一个名为consumer
,合约将存储在src/main/resources/contracts/producer/consumer/
) -
定义 Contract 后,将 producer 存根安装到本地存储,如下例所示:
$ cd src/main/resource/contracts/producer $ ./mvnw clean install
-
在使用者测试中设置 Spring Cloud Contract (SCC) Stub Runner,以:
-
从本地存储获取生产者存根。
-
在 stubs-per-consumer 模式下工作(这将启用消费者驱动的 Contract 模式)。
SCC 存根运行程序:
-
获取生产者存根。
-
使用创建器存根运行内存中 HTTP 服务器存根。 现在,您的测试与 HTTP 服务器存根通信,并且您的测试通过。
-
使用合同定义创建者的新合同创建对存储库的拉取请求。
-
对使用者代码进行分支,直到生成者团队合并其代码。
-
下面的 UML 图显示了使用者流:

4.3. 生产者流程
制片人:
-
使用合同定义接管对存储库的拉取请求。你可以的 从命令行,如下所示
$ git checkout -b the_branch_with_pull_request master git pull https://github.com/user_id/project_name.git the_branch_with_pull_request
-
安装合同定义,如下所示
$ ./mvnw clean install
-
设置插件以从 JAR 而不是 JAR 获取协定定义
src/test/resources/contracts
如下: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>
Gradlecontracts { // 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 }
-
运行生成以生成测试和存根,如下所示:
Maven 系列./mvnw clean install
Gradle./gradlew clean build
-
写入缺少的实现,以使测试通过。
-
将拉取请求合并到带有合同定义的存储库,如下所示:
$ 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
CI 系统使用合约定义构建项目,并使用 将合同定义设置为 Nexus 或 Artifactory。
-
切换到远程工作。
-
设置插件,以便不再从本地获取 Contract 定义 存储,但从远程位置存储,如下所示:
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>
Gradlecontracts { // 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 }
-
将创建者代码与新实现合并。
-
CI 系统:
-
生成项目。
-
生成测试、存根和存根 JAR。
-
将包含应用程序和存根的工件上传到 Nexus 或 Artifactory。
-
下面的 UML 图显示了生产者进程:

5. 消费者驱动的合约,生产者端的合约,推送到 Git
您可以阅读 消费者驱动型合同 (CDC) 的分步指南,其中合同位于生产者端,以查看在生产者端有合同的消费者驱动型合同流。
存根存储实现是一个 git 存储库。我们在 在 Git 中使用存根进行提供商合同测试 部分介绍了它的设置。
您可以在 文档的 How To 部分。
6. 在 Artifactory 中使用非 Spring 应用程序的存根进行提供者合同测试
6.1. 流程
您可以阅读开发您的第一个 Spring Cloud 基于契约的应用程序,以查看在 Nexus 或 Artifactor 中使用存根进行提供商契约测试的流程。
6.2. 设置 Consumer
对于使用者端,您可以使用 JUnit 规则。这样,你就不需要启动 Spring 上下文。下面的清单显示了这样的规则(在 JUnit4 和 JUnit 5 中);
@Rule
public StubRunnerRule rule = new StubRunnerRule()
.downloadStub("com.example","artifact-id", "0.0.1")
.repoRoot("git://[email protected]:spring-cloud-samples/spring-cloud-contract-nodejs-contracts-git.git")
.stubsMode(StubRunnerProperties.StubsMode.REMOTE);
@RegisterExtension
public StubRunnerExtension stubRunnerExtension = new StubRunnerExtension()
.downloadStub("com.example","artifact-id", "0.0.1")
.repoRoot("git://[email protected]:spring-cloud-samples/spring-cloud-contract-nodejs-contracts-git.git")
.stubsMode(StubRunnerProperties.StubsMode.REMOTE);
6.3. 设置 Producer
默认情况下,Spring Cloud Contract 插件使用 Rest Assure 的MockMvc
setup 的
生成的测试。由于非 Spring 应用程序不使用MockMvc
中,您可以更改testMode
自EXPLICIT
向绑定在特定端口的应用程序发送真实请求。
在此示例中,我们使用一个名为 Javalin 的框架来启动 非 Spring HTTP 服务器。
假设我们有以下应用程序:
package com.example.demo;
public class DemoApplication {
public static void main(String[] args) {
new DemoApplication().run(7000);
}
public Javalin start(int port) {
return Javalin.create().start(port);
}
public Javalin registerGet(Javalin app) {
return app.get("/", ctx -> ctx.result("Hello World"));
}
public Javalin run(int port) {
return registerGet(start(port));
}
}
Given that application, we can set up the plugin to use the EXPLICIT
mode (that is, to
send out requests to a real port), 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>
<baseClassForTests>com.example.demo.BaseClass</baseClassForTests>
<!-- This will setup the EXPLICIT mode for the tests -->
<testMode>EXPLICIT</testMode>
</configuration>
</plugin>
Gradle
contracts {
// This will setup the EXPLICIT mode for the tests
testMode = "EXPLICIT"
baseClassForTests = "com.example.demo.BaseClass"
}
The base class might resemble the following:
public class BaseClass {
Javalin app;
@Before
public void setup() {
// pick a random port
int port = SocketUtils.findAvailableTcpPort();
// start the application at a random port
this.app = start(port);
// tell Rest Assured where the started application is
RestAssured.baseURI = "http://localhost:" + port;
}
@After
public void close() {
// stop the server after each test
this.app.stop();
}
private Javalin start(int port) {
// reuse the production logic to start a server
return new DemoApplication().run(port);
}
}
With such a setup:
-
We have set up the Spring Cloud Contract plugin to use the EXPLICIT
mode to send real
requests instead of mocked ones.
-
We have defined a base class that:
-
Starts the HTTP server on a random port for each test.
-
Sets Rest Assured to send requests to that port.
-
Closes the HTTP server after each test.
7. Provider Contract Testing with Stubs in Artifactory in a Non-JVM World
In this flow, we assume that:
-
The API Producer and API Consumer are non-JVM applications.
-
The contract definitions are written in YAML.
-
The Stub Storage is Artifactory or Nexus.
-
Spring Cloud Contract Docker (SCC Docker) and Spring Cloud Contract Stub Runner Docker
(SCC Stub Runner Docker) images are used.
You can read more about how to use Spring Cloud Contract with Docker here.
Here, you can
read a blog post about how to use Spring Cloud Contract in a polyglot world.
Here, you can find
a sample of a NodeJS application that uses Spring Cloud Contract both as a producer and a
consumer.
7.1. Producer Flow
At a high level, the producer:
-
Writes contract definitions (for example, in YAML).
-
Sets up the build tool to:
-
Start the application with mocked services on a given port.
If mocking is not possible, you can set up the infrastructure and define tests in a stateful way.
-
Run the Spring Cloud Contract Docker image and pass the port of a running application as an environment variable.
The SCC Docker image:
* Generates the tests from the attached volume.
* Runs the tests against the running application.
Upon test completion, stubs get uploaded to a stub storage site (such as Artifactory or Git).
The following UML diagram shows the producer flow:
7.2. Consumer Flow
At a high level, the consumer:
-
Sets up the build tool to:
-
Start the Spring Cloud Contract Stub Runner Docker image and start the stubs.
The environment variables configure:
-
The stubs to fetch.
-
The location of the repositories.
Note that:
-
To use the local storage, you can also attach it as a volume.
-
The ports at which the stubs are running need to be exposed.
-
Run the application tests against the running stubs.
The following UML diagram shows the consumer flow:
8. Provider Contract Testing with REST Docs and Stubs in Nexus or Artifactory
In this flow, we do not use a Spring Cloud Contract plugin to generate tests and stubs. We write Spring RESTDocs, and, from them, we automatically generate stubs. Finally, we set up our builds to package the stubs and upload them to the stub storage site — in our case, Nexus or Artifactory.
8.1. Producer Flow
As a producer, we:
-
Write RESTDocs tests of our API.
-
Add Spring Cloud Contract Stub Runner starter to our build (spring-cloud-starter-contract-stub-runner
), as follows:
Maven
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Gradle
dependencies {
testImplementation 'org.springframework.cloud:spring-cloud-starter-contract-stub-runner'
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
-
We set up the build tool to package our stubs, as follows:
Maven
<!-- pom.xml -->
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<id>stub</id>
<phase>prepare-package</phase>
<goals>
<goal>single</goal>
</goals>
<inherited>false</inherited>
<configuration>
<attach>true</attach>
<descriptors>
${basedir}/src/assembly/stub.xml
</descriptors>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<!-- src/assembly/stub.xml -->
<assembly
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
<id>stubs</id>
<formats>
<format>jar</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<fileSet>
<directory>${project.build.directory}/generated-snippets/stubs</directory>
<outputDirectory>META-INF/${project.groupId}/${project.artifactId}/${project.version}/mappings</outputDirectory>
<includes>
<include>**/*</include>
</includes>
</fileSet>
</fileSets>
</assembly>
Gradle
task stubsJar(type: Jar) {
classifier = "stubs"
into("META-INF/${project.group}/${project.name}/${project.version}/mappings") {
include('**/*.*')
from("${project.buildDir}/generated-snippets/stubs")
}
}
// we need the tests to pass to build the stub jar
stubsJar.dependsOn(test)
bootJar.dependsOn(stubsJar)
Now, when we run the tests, stubs are automatically published and packaged.
The following UML diagram shows the producer flow:
8.2. Consumer Flow
Since the consumer flow is not affected by the tool used to generate the stubs, you can read Developing Your First Spring Cloud Contract-based Application to see the flow for consumer side of the provider contract testing with stubs in Nexus or Artifactory.
9. What to Read Next
You should now understand how you can use Spring Cloud Contract and some best practices that you
should follow. You can now go on to learn about specific
Spring Cloud Contract features, or you could
skip ahead and read about the advanced features of Spring Cloud Contract.