使用 Spring Cloud Contract

1. 在 Nexus 或 Artifactory 中使用 Stub 进行提供商合同测试

您可以查看开发第一个基于 Spring Cloud Contract 的应用程序链接,以查看在 Nexus 或 Artifactory 流程中使用存根进行提供商 Contract 测试。spring-doc.cadn.net.cn

2. 在 Git 中使用存根进行提供商合同测试

在这个流程中,我们执行提供者契约测试(生产者不知道消费者如何使用他们的 API)。存根将上传到单独的存储库(它们不会上传到 Artifactory 或 Nexus)。spring-doc.cadn.net.cn

2.1. 先决条件

在 git 中使用存根测试提供商合同之前,您必须提供 git 存储库 ,其中包含每个生产者的所有存根。有关此类项目的示例,请参阅此示例此示例。 将存根推送到那里的结果,存储库具有以下结构:spring-doc.cadn.net.cn

$ tree .
└── META-INF
   └── folder.with.group.id.as.its.name
       └── folder-with-artifact-id
           └── folder-with-version
               ├── contractA.groovy
               ├── contractB.yml
               └── contractC.groovy

您还必须提供设置了 Spring Cloud Contract Stub Runner 的使用者代码。为 此类项目的示例,请参阅此示例并搜索BeerControllerGitTest测试。您还必须提供具有 Spring Cloud 的生产者代码 合约设置,以及插件。有关此类项目的示例,请参阅此示例spring-doc.cadn.net.cn

2.2. 流程

该流程看起来与开发第一个基于 Spring Cloud Contract 的应用程序 中提供的流程完全相同。 但是Stub Storageimplementation 是一个 git 存储库。spring-doc.cadn.net.cn

您可以阅读有关设置 git 存储库和设置 consumer 和 producer 端的更多信息 在文档的 How To 页面中。spring-doc.cadn.net.cn

2.3. 消费者设置

为了从 git 存储库而不是 Nexus 或 Artifactory 获取存根,您需要 需要使用git协议的repositoryRoot属性。 以下示例显示了如何设置它:spring-doc.cadn.net.cn

注解
@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")
JUnit 4 规则
@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);
JUnit 5 扩展
@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,您需要 要使用gitprotocol 的 URL 中。此外,您需要明确告知 用于在构建过程结束时推送存根的插件。以下示例显示 如何在 Maven 和 Gradle 中执行此作:spring-doc.cadn.net.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>
        <!-- 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>
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://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 存储库的更多信息。spring-doc.cadn.net.cn

4. 具有外部存储库中的合同的消费者驱动合同

在这个流程中,我们执行 Consumer Driven Contract 测试。合同定义包括 存储在单独的存储库中。spring-doc.cadn.net.cn

4.1. 先决条件

要将使用者驱动的协定与外部存储库中保存的协定一起使用,您需要设置一个 git 存储库,该存储库:spring-doc.cadn.net.cn

有关更多信息,请参阅“作方法”部分。 其中描述了如何设置这样的存储库。 有关此类项目的示例,请参阅此示例spring-doc.cadn.net.cn

您还需要设置了 Spring Cloud Contract Stub Runner 的使用者代码。 有关此类项目的示例,请参阅此示例。 您还需要设置了 Spring Cloud Contract 的生产者代码以及插件。 有关此类项目的示例,请参阅此示例。 存根存储是 Nexus 或 Artifactory。spring-doc.cadn.net.cn

概括地说,流程如下:spring-doc.cadn.net.cn

  1. 使用者使用来自单独存储库的 Contract 定义。spring-doc.cadn.net.cn

  2. 一旦消费者的工作完成,就会在消费者上创建一个带有工作代码的分支 side 中,并向包含合同定义的单独存储库发出拉取请求。spring-doc.cadn.net.cn

  3. 创建者通过 Contract 接管对单独存储库的拉取请求 定义并在本地安装包含所有 Contract 的 JAR。spring-doc.cadn.net.cn

  4. 创建者从本地存储的 JAR 生成测试,并写入缺失的 implementation 使测试通过。spring-doc.cadn.net.cn

  5. 创建者的工作完成后,对包含该 Contract definitions 被合并。spring-doc.cadn.net.cn

  6. 在 CI 工具使用合约定义构建存储库并使用 JAR 合约定义上传到 Nexus 或 Artifactory,生产者可以合并其分支。spring-doc.cadn.net.cn

  7. 最后,消费者可以切换到在线工作,从 remote 位置,并且该分支可以合并到 master 中。spring-doc.cadn.net.cn

4.2. 消费者流程

  1. 编写一个测试,该测试将向创建者发送请求。spring-doc.cadn.net.cn

    由于没有服务器存在,测试失败。spring-doc.cadn.net.cn

  2. 克隆包含合同定义的存储库。spring-doc.cadn.net.cn

  3. 将需求设置为文件夹下的合同,并将使用者名称作为生产者的子文件夹。spring-doc.cadn.net.cn

    例如,对于名为producer和一个名为consumer,合约将存储在src/main/resources/contracts/producer/consumer/)spring-doc.cadn.net.cn

  4. 定义 Contract 后,将 producer 存根安装到本地存储,如下例所示:spring-doc.cadn.net.cn

    $ cd src/main/resource/contracts/producer
    $ ./mvnw clean install
  5. 在使用者测试中设置 Spring Cloud Contract (SCC) Stub Runner,以:spring-doc.cadn.net.cn

下面的 UML 图显示了使用者流:spring-doc.cadn.net.cn

流程概述 使用者 CDC 外部使用者

4.3. 生产者流程

  1. 使用合同定义接管对存储库的拉取请求。你可以的 从命令行,如下所示spring-doc.cadn.net.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. 安装合同定义,如下所示spring-doc.cadn.net.cn

    $ ./mvnw clean install
  3. 设置插件以从 JAR 而不是 JAR 获取协定定义src/test/resources/contracts如下:spring-doc.cadn.net.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. 运行生成以生成测试和存根,如下所示:spring-doc.cadn.net.cn

    Maven 系列
    ./mvnw clean install
    Gradle
    ./gradlew clean build
    
  5. 写入缺少的实现,以使测试通过。spring-doc.cadn.net.cn

  6. 将拉取请求合并到带有合同定义的存储库,如下所示:spring-doc.cadn.net.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

    CI 系统使用合约定义构建项目,并使用 将合同定义设置为 Nexus 或 Artifactory。spring-doc.cadn.net.cn

  7. 切换到远程工作。spring-doc.cadn.net.cn

  8. 设置插件,以便不再从本地获取 Contract 定义 存储,但从远程位置存储,如下所示:spring-doc.cadn.net.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. 将创建者代码与新实现合并。spring-doc.cadn.net.cn

  10. CI 系统:spring-doc.cadn.net.cn

下面的 UML 图显示了生产者进程:spring-doc.cadn.net.cn

流程概述 使用者 CDC 外部生产者

5. 消费者驱动的合约,生产者端的合约,推送到 Git

您可以阅读 消费者驱动型合同 (CDC) 的分步指南,其中合同位于生产者端,以查看在生产者端有合同的消费者驱动型合同流。spring-doc.cadn.net.cn

存根存储实现是一个 git 存储库。我们在 在 Git 中使用存根进行提供商合同测试 部分介绍了它的设置。spring-doc.cadn.net.cn

您可以在 文档的 How To 部分spring-doc.cadn.net.cn

6. 在 Artifactory 中使用非 Spring 应用程序的存根进行提供者合同测试

6.1. 流程

您可以阅读开发您的第一个 Spring Cloud 基于契约的应用程序,以查看在 Nexus 或 Artifactor 中使用存根进行提供商契约测试的流程。spring-doc.cadn.net.cn

6.2. 设置 Consumer

对于使用者端,您可以使用 JUnit 规则。这样,你就不需要启动 Spring 上下文。下面的清单显示了这样的规则(在 JUnit4 和 JUnit 5 中);spring-doc.cadn.net.cn

JUnit 4 规则
@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);
JUnit 5 扩展
@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 的MockMvcsetup 的 生成的测试。由于非 Spring 应用程序不使用MockMvc中,您可以更改testModeEXPLICIT向绑定在特定端口的应用程序发送真实请求。spring-doc.cadn.net.cn

在此示例中,我们使用一个名为 Javalin 的框架来启动 非 Spring HTTP 服务器。spring-doc.cadn.net.cn

假设我们有以下应用程序:spring-doc.cadn.net.cn

package com.example.demo;

import io.javalin.Javalin;

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

import io.javalin.Javalin;
import io.restassured.RestAssured;
import org.junit.After;
import org.junit.Before;
import org.springframework.util.SocketUtils;

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

7. Provider Contract Testing with Stubs in Artifactory in a Non-JVM World

In this flow, we assume that:spring-doc.cadn.net.cn

You can read more about how to use Spring Cloud Contract with Docker here.spring-doc.cadn.net.cn

Here, you can read a blog post about how to use Spring Cloud Contract in a polyglot world.spring-doc.cadn.net.cn

Here, you can find a sample of a NodeJS application that uses Spring Cloud Contract both as a producer and a consumer.spring-doc.cadn.net.cn

7.1. Producer Flow

At a high level, the producer:spring-doc.cadn.net.cn

  1. Writes contract definitions (for example, in YAML).spring-doc.cadn.net.cn

  2. Sets up the build tool to:spring-doc.cadn.net.cn

    1. Start the application with mocked services on a given port.spring-doc.cadn.net.cn

      If mocking is not possible, you can set up the infrastructure and define tests in a stateful way.spring-doc.cadn.net.cn

    2. Run the Spring Cloud Contract Docker image and pass the port of a running application as an environment variable.spring-doc.cadn.net.cn

The SCC Docker image: * Generates the tests from the attached volume. * Runs the tests against the running application.spring-doc.cadn.net.cn

Upon test completion, stubs get uploaded to a stub storage site (such as Artifactory or Git).spring-doc.cadn.net.cn

The following UML diagram shows the producer flow:spring-doc.cadn.net.cn

flows provider non jvm producer

7.2. Consumer Flow

At a high level, the consumer:spring-doc.cadn.net.cn

  1. Sets up the build tool to:spring-doc.cadn.net.cn

  2. Run the application tests against the running stubs.spring-doc.cadn.net.cn

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

flows provider non jvm consumer

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

8.1. Producer Flow

As a producer, we:spring-doc.cadn.net.cn

  1. Write RESTDocs tests of our API.spring-doc.cadn.net.cn

  2. Add Spring Cloud Contract Stub Runner starter to our build (spring-cloud-starter-contract-stub-runner), as follows:spring-doc.cadn.net.cn

    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}"
        }
    }
    
  3. We set up the build tool to package our stubs, as follows:spring-doc.cadn.net.cn

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

The following UML diagram shows the producer flow:spring-doc.cadn.net.cn

flows provider rest docs producer

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

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