Gradle 项目

先决条件

要将 Spring Cloud Contract Verifier 与 WireMock 一起使用,必须使用 Gradle 或 Maven 插件。

如果要在项目中使用 Spock,必须单独添加 spock-corespock-spring 模块。有关更多信息,请参阅 Spock 的文档

添加带有依赖的 Gradle 插件

要添加带有依赖的 Gradle 插件,可以使用类似以下代码

插件 DSL GA 版本
// build.gradle
plugins {
  id "groovy"
  // this will work only for GA versions of Spring Cloud Contract
  id "org.springframework.cloud.contract" version "$\{GAVerifierVersion}"
}

dependencyManagement {
	imports {
		mavenBom "org.springframework.cloud:spring-cloud-contract-dependencies:$\{GAVerifierVersion}"
	}
}

dependencies {
	testImplementation "org.apache.groovy:groovy-all:$\{groovyVersion}"
	// example with adding Spock core and Spock Spring
	testImplementation "org.spockframework:spock-core:$\{spockVersion}"
	testImplementation "org.spockframework:spock-spring:$\{spockVersion}"
	testImplementation 'org.springframework.cloud:spring-cloud-starter-contract-verifier'
}
插件 DSL 非 GA 版本
// settings.gradle
pluginManagement {
	plugins {
		id "org.springframework.cloud.contract" version "$\{verifierVersion}"
	}
    repositories {
        // to pick from local .m2
        mavenLocal()
        // for snapshots
        maven { url "https://repo.spring.io/snapshot" }
        // for milestones
        maven { url "https://repo.spring.io/milestone" }
        // for GA versions
        gradlePluginPortal()
    }
}

// build.gradle
plugins {
  id "groovy"
  id "org.springframework.cloud.contract"
}

dependencyManagement {
	imports {
		mavenBom "org.springframework.cloud:spring-cloud-contract-dependencies:$\{verifierVersion}"
	}
}

dependencies {
	testImplementation "org.apache.groovy:groovy-all:$\{groovyVersion}"
	// example with adding Spock core and Spock Spring
	testImplementation "org.spockframework:spock-core:$\{spockVersion}"
	testImplementation "org.spockframework:spock-spring:$\{spockVersion}"
	testImplementation 'org.springframework.cloud:spring-cloud-starter-contract-verifier'
}
传统插件应用
// build.gradle
buildscript {
	repositories {
		mavenCentral()
	}
	dependencies {
		classpath "org.springframework.boot:spring-boot-gradle-plugin:$\{springboot_version}"
		classpath "org.springframework.cloud:spring-cloud-contract-gradle-plugin:$\{verifier_version}"
        // here you can also pass additional dependencies such as Kotlin spec e.g.:
        // classpath "org.springframework.cloud:spring-cloud-contract-spec-kotlin:$\{verifier_version}"
	}
}

apply plugin: 'groovy'
apply plugin: 'org.springframework.cloud.contract'

dependencyManagement {
	imports {
		mavenBom "org.springframework.cloud:spring-cloud-contract-dependencies:$\{verifier_version}"
	}
}

dependencies {
	testImplementation "org.apache.groovy:groovy-all:$\{groovyVersion}"
	// example with adding Spock core and Spock Spring
	testImplementation "org.spockframework:spock-core:$\{spockVersion}"
	testImplementation "org.spockframework:spock-spring:$\{spockVersion}"
	testImplementation 'org.springframework.cloud:spring-cloud-starter-contract-verifier'
}

Gradle 和 Rest Assured 2.0

默认情况下,Rest Assured 3.x 已添加到类路径中。但是,要使用 Rest Assured 2.x,可以添加它代替,如以下列表所示

buildscript {
	repositories {
		mavenCentral()
	}
	dependencies {
		classpath "org.springframework.boot:spring-boot-gradle-plugin:$\{springboot_version}"
		classpath "org.springframework.cloud:spring-cloud-contract-gradle-plugin:$\{verifier_version}"
	}
}

dependencies {
    // all dependencies
    // you can exclude rest-assured from spring-cloud-contract-verifier
    testCompile "com.jayway.restassured:rest-assured:2.5.0"
    testCompile "com.jayway.restassured:spring-mock-mvc:2.5.0"
}

这样,插件会自动检测到类路径中存在 Rest Assured 2.x,并相应地修改导入。

Gradle 的快照版本

可以将额外的快照仓库添加到 settings.gradle 中以使用快照版本,这些版本在每次成功构建后自动上传。

添加存根

默认情况下,Spring Cloud Contract Verifier 会在 src/contractTest/resources/contracts 目录中查找存根。出于过渡目的,插件也会在 src/test/resources/contracts 中查找合约,但该目录自 Spring Cloud Contract 3.0.0 起已弃用。

还应注意,使用这个新的 Gradle 源集时,您还应该将合约测试中使用的任何基类迁移到 src/contractTest/{language},其中 {language} 应根据需要替换为 Java 或 Groovy。

包含存根定义的目录被视为类名,每个存根定义被视为一个测试。Spring Cloud Contract Verifier 假定它至少包含一层目录,这些目录将用作测试类名。如果存在多层嵌套目录,除了最后一层之外的所有目录都将用作包名。考虑以下结构

src/contractTest/resources/contracts/myservice/shouldCreateUser.groovy
src/contractTest/resources/contracts/myservice/shouldReturnUser.groovy

给定上述结构,Spring Cloud Contract Verifier 会创建一个名为 defaultBasePackage.MyService 的测试类,其中包含两个方法

  • shouldCreateUser()

  • shouldReturnUser()

运行插件

插件将自身注册到在 check 任务之前调用。如果希望它成为构建过程的一部分,则无需执行其他操作。如果只想生成测试,请调用 generateContractTests 任务。

默认设置

默认的 Gradle 插件设置创建了构建的以下 Gradle 部分(伪代码)

contracts {
    testFramework ='JUNIT'
    testMode = 'MockMvc'
    generatedTestJavaSourcesDir = project.file("$\{project.buildDir}/generated-test-sources/contractTest/java")
    generatedTestGroovySourcesDir = project.file("$\{project.buildDir}/generated-test-sources/contractTest/groovy")
    generatedTestResourcesDir = project.file("$\{project.buildDir}/generated-test-resources/contracts")
    contractsDslDir = project.file("$\{project.projectDir}/src/contractTest/resources/contracts")
    basePackageForTests = 'org.springframework.cloud.verifier.tests'
    stubsOutputDir = project.file("$\{project.buildDir}/stubs")
    sourceSet = null
}

def verifierStubsJar = tasks.register(type: Jar, name: 'verifierStubsJar', dependsOn: 'generateClientStubs') {
    baseName = project.name
    classifier = contracts.stubsSuffix
    from contractVerifier.stubsOutputDir
}

def copyContracts = tasks.register(type: Copy, name: 'copyContracts') {
    from contracts.contractsDslDir
    into contracts.stubsOutputDir
}

verifierStubsJar.dependsOn copyContracts

配置插件

要更改默认配置,可以向 Gradle 配置添加 contracts 代码片段,如以下列表所示

contracts {
	testMode = 'MockMvc'
	baseClassForTests = 'org.mycompany.tests'
	generatedTestJavaSourcesDir = project.file('src/generatedContract')
}

要从远程源下载合约,可以根据需要使用以下代码片段

contracts {
    // If your contracts exist in a JAR archive published to a Maven repository
    contractDependency {
        stringNotation = ''
        // OR
        groupId = ''
        artifactId = ''
        version = ''
        classifier = ''
    }

    // If your contracts exist in a Git SCM repository
    contractRepository {
        repositoryUrl = ''
        // username = ''
        // password = ''
    }

    // controls the nested location to find the contracts in either the JAR or Git SCM source
    contractsPath = ''
}

由于我们使用了 Gradle 的 Jar 打包任务,因此您可能希望利用一些选项和功能来进一步扩展 verifierStubsJar 创建的内容。为此,您可以使用 Gradle 直接提供的原生机制来定制现有任务,如下所示

为了示例目的,我们希望向 verifierStubsJar 添加一个 git.properties 文件。
verifierStubsJar {
    from("$\{buildDir}/resources/main/") {
        include("git.properties")
    }
}

还应注意,自 3.0.0 版本起,默认发布已禁用。因此,这意味着您可以创建任何命名的 jar 并像往常通过 Gradle 配置选项那样发布它。这意味着您可以按照自己的方式定制构建一个 jar 文件,并发布它,以完全控制 jar 的布局和内容。

配置选项

  • testMode: 定义验收测试模式。默认模式为 MockMvc,基于 Spring 的 MockMvc。它也可以更改为 WebTestClient、JaxRsClient 或 Explicit(用于真实的 HTTP 调用)。

  • imports: 创建一个数组,其中包含应包含在生成的测试中的导入(例如,['org.myorg.Matchers'])。默认情况下,它创建一个空数组。

  • staticImports: 创建一个数组,其中包含应包含在生成的测试中的静态导入(例如,['org.myorg.Matchers.*'])。默认情况下,它创建一个空数组。

  • basePackageForTests: 指定所有生成测试的基包。如果未设置,则从 baseClassForTests 的包和从 packageWithBaseClasses 中获取值。如果这些值都未设置,则值设置为 org.springframework.cloud.contract.verifier.tests

  • baseClassForTests: 为所有生成的测试创建一个基类。默认情况下,如果您使用 Spock 类,则该类为 spock.lang.Specification

  • packageWithBaseClasses: 定义所有基类所在的包。此设置优先于 baseClassForTests

  • baseClassMappings: 显式地将合约包映射到基类的完全限定名 (FQN)。此设置优先于 packageWithBaseClassesbaseClassForTests

  • ignoredFiles: 使用 Antmatcher 允许定义应跳过处理的存根文件。默认情况下,它是一个空数组。

  • contractsDslDir: 指定包含使用 GroovyDSL 编写的合约的目录。默认情况下,其值为 $projectDir/src/contractTest/resources/contracts

  • generatedTestSourcesDir: 指定应放置从 Groovy DSL 生成的测试的测试源目录。(已弃用)

  • generatedTestJavaSourcesDir: 指定应放置从 Groovy DSL 生成的 Java/JUnit 测试的测试源目录。默认情况下,其值为 $buildDir/generated-tes-sources/contractTest/java

  • generatedTestGroovySourcesDir: 指定应放置从 Groovy DSL 生成的 Groovy/Spock 测试的测试源目录。默认情况下,其值为 $buildDir/generated-test-sources/contractTest/groovy

  • generatedTestResourcesDir: 指定应放置从 Groovy DSL 生成的测试所使用的资源的测试资源目录。默认情况下,其值为 $buildDir/generated-test-resources/contractTest

  • stubsOutputDir: 指定应放置从 Groovy DSL 生成的 WireMock 存根的目录。

  • testFramework: 指定要使用的目标测试框架。目前支持 Spock、JUnit 4 (TestFramework.JUNIT) 和 JUnit 5,其中 JUnit 4 是默认框架。

  • contractsProperties: 一个 Map,包含要传递给 Spring Cloud Contract 组件的属性。这些属性可能由(例如)内置或自定义的 Stub 下载器使用。

  • sourceSet: 存储合约的源集。如果未提供,将假定为 contractTest(例如,JUnit 为 project.sourceSets.contractTest.java,Spock 为 project.sourceSets.contractTest.groovy)。

您可以使用以下属性来指定包含合约的 JAR 的位置

  • contractDependency: 指定提供 groupid:artifactid:version:classifier 坐标的依赖项。可以使用 contractDependency closure 来进行设置。

  • contractsPath: 指定 jar 的路径。如果下载了合约依赖项,路径默认为 groupid/artifactid,其中 groupid 是斜杠分隔的。否则,它会扫描提供的目录下的合约。

  • contractsMode: 指定下载合约的模式(例如,JAR 是否离线、远程等)。

  • deleteStubsAfterTest: 如果设置为 false,则不会从临时目录中删除任何下载的合约。

  • failOnNoContracts: 启用时,如果找不到合约,将抛出异常。默认为 true

  • failOnInProgress: 如果设置为 true,则如果找到任何正在进行中的合约,它们将导致构建失败。在生产者端,您需要明确表明您有正在进行中的合约,并考虑到这可能会导致消费者端的测试结果出现误报。默认为 true

还有一个 contractRepository { …​ } closure,其中包含以下属性

  • repositoryUrl: 包含合约定义的仓库 URL

  • username : 仓库用户名

  • password : 仓库密码

  • proxyPort : 代理端口

  • proxyHost : 代理主机

  • cacheDownloadedContracts : 如果设置为 true,则缓存非快照合约 artifacts 下载到的文件夹。默认为 true

您还可以在插件中开启以下实验性功能

  • convertToYaml: 将所有 DSL 转换为声明式 YAML 格式。这在使用外部库编写 Groovy DSL 时非常有用。通过开启此功能(将其设置为 true),消费者端无需添加库依赖。

  • assertJsonSize: 您可以在生成的测试中检查 JSON 数组的大小。此功能默认禁用。

所有测试的单一基类

在 MockMvc(默认)中使用 Spring Cloud Contract Verifier 时,需要为所有生成的验收测试创建一个基本规范。在此类中,您需要指向应被验证的端点。以下示例展示了如何做到这一点

abstract class BaseMockMvcSpec extends Specification {

	def setup() {
		RestAssuredMockMvc.standaloneSetup(new PairIdController())
	}

	void isProperCorrelationId(Integer correlationId) {
		assert correlationId == 123456
	}

	void isEmpty(String value) {
		assert value == null
	}

}

如果使用 Explicit 模式,可以使用基类初始化整个被测试的应用程序,就像在常规集成测试中一样。如果使用 JAXRSCLIENT 模式,此基类还应包含一个受保护的 WebTarget webTarget 字段。目前,测试 JAX-RS API 的唯一选项是启动 Web 服务器。

不同合约使用不同的基类

如果不同合约使用不同的基类,可以告知 Spring Cloud Contract 插件哪个类应该被自动生成的测试所扩展。您有两种选择

  • 通过提供 packageWithBaseClasses 遵循约定

  • 通过使用 baseClassMappings 提供显式映射

通过约定

约定是这样的:如果您在(例如)src/contractTest/resources/contract/foo/bar/baz/ 中有一个合约,并将 packageWithBaseClasses 属性的值设置为 com.example.base,那么 Spring Cloud Contract Verifier 会假定在 com.example.base 包下有一个 BarBazBase 类。换句话说,系统会取包的最后两部分(如果存在),并形成一个带有 Base 后缀的类。此规则优先于 baseClassForTests

通过映射

您可以手动将合约包的正则表达式映射到匹配合约的基类的完全限定名。您必须提供一个名为 baseClassMappings 的列表,该列表由 baseClassMapping 对象组成,这些对象采用 contractPackageRegexbaseClassFQN 的映射。

假设您在以下目录中有合约

  • src/contractTest/resources/contract/com/

  • src/contractTest/resources/contract/foo/

通过提供 baseClassForTests,我们在映射失败的情况下有一个回退选项。(您也可以提供 packageWithBaseClasses 作为回退。)这样,从 src/contractTest/resources/contract/com/ 合约生成的测试将扩展 com.example.ComBase,而其余测试将扩展 com.example.FooBase

调用生成的测试

为确保提供者端符合您定义的合约,需要运行以下命令

./gradlew contractTest

将存根发布到 Artifact 仓库

如果使用二进制 artifact 仓库来保存存根,则需要配置 Gradle 的发布部分以包含 verifierStubsJar。为此,您可以使用以下示例配置

apply plugin: 'maven-publish'

publishing {
    publications {
        maven(MavenPublication) {
            // other configuration

            artifact verifierStubsJar
        }
    }
}

自 3.0.0 版本起,内部存根发布已被弃用并默认禁用。建议将 verifierStubsJar 与您自己的发布一同包含。

将存根推送到 SCM

如果使用 SCM 仓库来保存合约和存根,您可能希望自动化将存根推送到仓库的步骤。为此,可以通过运行以下命令来调用 pushStubsToScm 任务

$ ./gradlew pushStubsToScm

使用 SCM 存根下载器 下,您可以找到所有可能的配置选项,这些选项可以通过 contractsProperties 字段(例如,contracts { contractsProperties = [foo:"bar"] })、通过 contractsProperties 方法(例如,contracts { contractsProperties([foo:"bar"]) })传递,或者通过系统属性或环境变量传递。

消费者端的 Spring Cloud Contract Verifier

在消费服务中,您需要像提供者一样精确配置 Spring Cloud Contract Verifier 插件。如果不想使用 Stub Runner,需要复制存储在 src/contractTest/resources/contracts 中的合约,并使用以下命令生成 WireMock JSON 存根

./gradlew generateClientStubs
必须设置 stubsOutputDir 选项才能使存根生成工作。

存在时,您可以在自动化测试中使用 JSON 存根来消费服务。以下示例展示了如何做到这一点

@ContextConfiguration(loader == SpringApplicationContextLoader, classes == Application)
class LoanApplicationServiceSpec extends Specification {

 @ClassRule
 @Shared
 WireMockClassRule wireMockRule == new WireMockClassRule()

 @Autowired
 LoanApplicationService sut

 def 'should successfully apply for loan'() {
   given:
 	LoanApplication application =
			new LoanApplication(client: new Client(clientPesel: '12345678901'), amount: 123.123)
   when:
	LoanApplicationResult loanApplication == sut.loanApplication(application)
   then:
	loanApplication.loanApplicationStatus == LoanApplicationStatus.LOAN_APPLIED
	loanApplication.rejectionReason == null
 }
}

在前面的示例中,LoanApplication 对 FraudDetection 服务发起调用。此请求由配置了由 Spring Cloud Contract Verifier 生成的存根的 WireMock 服务器处理。