Contract DSL

Spring Cloud Contract 支持以以下语言编写的 DSL:

Spring Cloud Contract supports DSLs written in the following languages:

  • Groovy

  • YAML

  • Java

  • Kotlin

Spring Cloud Contract 支持在单个文件中定义多个合同(在 Groovy 中,返回列表而不是单个合同)。

Spring Cloud Contract supports defining multiple contracts in a single file (In Groovy return a list instead of a single contract).

以下示例显示契约定义:

The following example shows a contract definition:

Groovy
Unresolved directive in project-features-contract.adoc - include::{verifier_root_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/SpringTestMethodBodyBuildersSpec.groovy[]
YAML
Unresolved directive in project-features-contract.adoc - include::{verifier_root_path}/src/test/resources/yml/contract_rest.yml[]
Java
Unresolved directive in project-features-contract.adoc - include::{verifier_root_path}/src/test/resources/contractsToCompile/contract_rest.java[]
Kotlin
Unresolved directive in project-features-contract.adoc - include::{verifier_root_path}/src/test/resources/kotlin/contract_rest.kts[]

可以使用以下 standalone Maven 命令将契约编译为存根映射:

You can compile contracts to stubs mapping by using the following standalone Maven command:

mvn org.springframework.cloud:spring-cloud-contract-maven-plugin:convert

Contract DSL in Groovy

Contract DSL in Groovy

如果你不熟悉 Groovy,请不要担心。也可以在 Groovy DSL 文件中使用 Java 语法。

If you are not familiar with Groovy, do not worry. You can use Java syntax in the Groovy DSL files as well.

如果你决定用 Groovy 编写契约,即使你以前从未使用过 Groovy,也不必感到惊慌。实际上并不需要了解该语言,因为 Contract DSL 只使用它的一小部分(仅限于文字、方法调用和闭包)。此外,DSL 是静态类型的,即使不了解 DSL 本身,也可以让程序员阅读。

If you decide to write the contract in Groovy, do not be alarmed if you have not used Groovy before. Knowledge of the language is not really needed, as the Contract DSL uses only a tiny subset of it (only literals, method calls, and closures). Also, the DSL is statically typed, to make it programmer-readable without any knowledge of the DSL itself.

请记住,在 Groovy 合约文件中,你必须为 Contract 类和 make 静态导入(例如 org.springframework.cloud.spec.Contract.make { …​ })提供限定名称。你还可以为 Contract 类 (import org.springframework.cloud.spec.Contract) 提供导入,然后调用 Contract.make { …​ }

Remember that, inside the Groovy contract file, you have to provide the fully qualified name to the Contract class and make static imports, such as org.springframework.cloud.spec.Contract.make { …​ }. You can also provide an import to the Contract class (import org.springframework.cloud.spec.Contract) and then call Contract.make { …​ }.

Contract DSL in Java

Contract DSL in Java

要在 Java 中编写契约定义,你需要创建一个类来实现 Supplier<Contract> 接口(用于单个契约)หรือ Supplier<Collection<Contract>>(用于多个契约)。

To write a contract definition in Java, you need to create a class that implements either the Supplier<Contract> interface (for a single contract) or Supplier<Collection<Contract>> (for multiple contracts).

你也可以在 src/test/java(例如 src/test/java/contracts)下编写契约定义,这样就无需修改项目中的类路径。在这种情况下,你必须向 Spring Cloud Contract 插件提供契约定义的新位置。

You can also write the contract definitions under src/test/java (for example, src/test/java/contracts) so that you do not have to modify the classpath of your project. In this case, you have to provide a new location of contract definitions to your Spring Cloud Contract plugin.

以下示例(在 Maven 和 Gradle 中)将在 src/test/java 下提供契约定义:

The following example (in both Maven and Gradle) has the contract definitions under src/test/java:

Maven
<plugin>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-contract-maven-plugin</artifactId>
    <version>${spring-cloud-contract.version}</version>
    <extensions>true</extensions>
    <configuration>
        <contractsDirectory>src/test/java/contracts</contractsDirectory>
    </configuration>
</plugin>
Gradle
contracts {
	contractsDslDir = new File(project.rootDir, "src/test/java/contracts")
}

Contract DSL in Kotlin

Contract DSL in Kotlin

要开始用 Kotlin 编写合同,你需要从(新创建的)Kotlin Script 文件(.kts)开始。与 Java DSL 一样,你可以将你的合同放在你选择的任何目录中。默认情况下,Maven 插件将查看 src/test/resources/contracts 目录,Gradle 插件将查看 src/contractTest/resources/contracts 目录。

To get started with writing contracts in Kotlin, you need to start with a (newly created) Kotlin Script file (.kts). As with the Java DSL, you can put your contracts in any directory of your choice. By default, the Maven plugin will look at the src/test/resources/contracts directory and Gradle plugin will look at the src/contractTest/resources/contracts directory.

自 3.0.0 版本起,Gradle 插件也会查看 src/test/resources/contracts 旧版目录以完成迁移。如果在该目录中找到合约,构建期间就会记录警告。

Since 3.0.0, the Gradle plugin will also look at the legacy directory src/test/resources/contracts for migration purposes. When contracts are found in this directory, a warning will be logged during your build.

你需要明确地将 spring-cloud-contract-spec-kotlin 依赖项传递给你的项目插件设置。以下示例(在 Maven 和 Gradle 中)展示了如何执行此操作:

You need to explicitly pass the spring-cloud-contract-spec-kotlin dependency to your project plugin setup. The following example (in both Maven and Gradle) shows how to do so:

Maven
<plugin>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-contract-maven-plugin</artifactId>
    <version>${spring-cloud-contract.version}</version>
    <extensions>true</extensions>
    <configuration>
        <!-- some config -->
    </configuration>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-contract-spec-kotlin</artifactId>
            <version>${spring-cloud-contract.version}</version>
        </dependency>
    </dependencies>
</plugin>

<dependencies>
        <!-- Remember to add this for the DSL support in the IDE and on the consumer side -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-contract-spec-kotlin</artifactId>
            <scope>test</scope>
        </dependency>
</dependencies>
Gradle
buildscript {
    repositories {
        // ...
    }
	dependencies {
		classpath "org.springframework.cloud:spring-cloud-contract-gradle-plugin:$\{scContractVersion}"
	}
}

dependencies {
    // ...

    // Remember to add this for the DSL support in the IDE and on the consumer side
    testImplementation "org.springframework.cloud:spring-cloud-contract-spec-kotlin"
    // Kotlin versions are very particular down to the patch version. The <kotlin_version> needs to be the same as you have imported for your project.
    testImplementation "org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:<kotlin_version>"
}

请记住,在 Kotlin 脚本文件中,你必须为 ContractDSL 类提供限定名称。你通常会像这样使用它的 contract 函数:org.springframework.cloud.contract.spec.ContractDsl.contract { …​ }。你还可以为 contract 函数 (import org.springframework.cloud.contract.spec.ContractDsl.Companion.contract) 提供导入,然后调用 contract { …​ }

Remember that, inside the Kotlin Script file, you have to provide the fully qualified name to the ContractDSL class. Generally you would use its contract function as follows: org.springframework.cloud.contract.spec.ContractDsl.contract { …​ }. You can also provide an import to the contract function (import org.springframework.cloud.contract.spec.ContractDsl.Companion.contract) and then call contract { …​ }.

Contract DSL in YAML

Contract DSL in YAML

要查看 YAML 合同的架构,请访问 YML Schema 页面。

To see a schema of a YAML contract, visit the YML Schema page.

Limitations

Limitations

验证 JSON 数组大小的支持为实验性功能。如果你想启用它,请将以下系统属性的值设为 truespring.cloud.contract.verifier.assert.size。此功能的默认设置是 false。你还可以设置插件配置中的 assertJsonSize 属性。

The support for verifying the size of JSON arrays is experimental. If you want to turn it on, set the value of the following system property to true: spring.cloud.contract.verifier.assert.size. By default, this feature is set to false. You can also set the assertJsonSize property in the plugin configuration.

由于 JSON 结构可能有任意形式,使用 Groovy DSL 和 GString 中的 value(consumer(…​), producer(…​)) 符号时可能无法正确解析它。因此,你应该使用 Groovy Map 符号。

Because JSON structure can have any form, it can be impossible to parse it properly when using the Groovy DSL and the value(consumer(…​), producer(…​)) notation in GString. That is why you should use the Groovy Map notation.

Multiple Contracts in One File

Multiple Contracts in One File

你可以在一个文件中定义多个合同。此类合同可能类似于以下示例:

You can define multiple contracts in one file. Such a contract might resemble the following example:

Groovy
Unresolved directive in _dsl-multiple.adoc - include::{plugins_path}/spring-cloud-contract-maven-plugin/src/test/projects/multiple-contracts/src/test/resources/contracts/com/hello/v1/WithList.groovy[]
YAML
Unresolved directive in _dsl-multiple.adoc - include::{verifier_root_path}/src/test/resources/yml/multiple_contracts.yml[]
Java
class contract implements Supplier<Collection<Contract>> {

	@Override
	public Collection<Contract> get() {
		return Arrays.asList(
            Contract.make(c -> {
            	c.name("should post a user");
                // ...
            }), Contract.make(c -> {
                // ...
            }), Contract.make(c -> {
                // ...
            })
		);
	}

}
Kotlin
import org.springframework.cloud.contract.spec.ContractDsl.Companion.contract

arrayOf(
    contract {
        name("should post a user")
        // ...
    },
    contract {
        // ...
    },
    contract {
        // ...
    }
}

在前面的示例中,一个合同具有 name 字段,另一个没有。这会导致生成两个看起来像以下的测试:

In the preceding example, one contract has the name field and the other does not. This leads to generation of two tests that look like the following:

package org.springframework.cloud.contract.verifier.tests.com.hello;

import com.example.TestBase;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import com.jayway.restassured.module.mockmvc.specification.MockMvcRequestSpecification;
import com.jayway.restassured.response.ResponseOptions;
import org.junit.Test;

import static com.jayway.restassured.module.mockmvc.RestAssuredMockMvc.*;
import static com.toomuchcoding.jsonassert.JsonAssertion.assertThatJson;
import static org.assertj.core.api.Assertions.assertThat;

public class V1Test extends TestBase {

	@Test
	public void validate_should_post_a_user() throws Exception {
		// given:
			MockMvcRequestSpecification request = given();

		// when:
			ResponseOptions response = given().spec(request)
					.post("/users/1");

		// then:
			assertThat(response.statusCode()).isEqualTo(200);
	}

	@Test
	public void validate_withList_1() throws Exception {
		// given:
			MockMvcRequestSpecification request = given();

		// when:
			ResponseOptions response = given().spec(request)
					.post("/users/2");

		// then:
			assertThat(response.statusCode()).isEqualTo(200);
	}

}

请注意,对于具有 name 字段的合同,生成的测试方法被命名为 validate_should_post_a_user。未具有 name 字段的那个被称为 validate_withList_1。它对应于文件 WithList.groovy 的名称和列表中合同的索引。

Notice that, for the contract that has the name field, the generated test method is named validate_should_post_a_user. The one that does not have the name field is called validate_withList_1. It corresponds to the name of the file WithList.groovy and the index of the contract in the list.

生成的存根显示在以下示例中:

The generated stubs are shown in the following example:

should post a user.json
1_WithList.json

第一个文件从契约获取了 name 参数。第二个文件使用索引(本例中,契约在文件契约列表中的索引为 1)作为前缀获取契约文件名(WithList.groovy)。

The first file got the name parameter from the contract. The second got the name of the contract file (WithList.groovy) prefixed with the index (in this case, the contract had an index of 1 in the list of contracts in the file).

命名合同更好,因为这样做让你的测试更具意义。

It is much better to name your contracts, because doing so makes your tests far more meaningful.

Stateful Contracts

Stateful Contracts

有状态合约(也称为场景)是应按顺序读取的合约定义。这在以下情况下可能有用:

Stateful contracts (also known as scenarios) are contract definitions that should be read in order. This might be useful in the following situations:

  • You want to invoke the contract in a precisely defined order, since you use Spring Cloud Contract to test your stateful application.

我们非常不建议你这样做,因为合约测试应该是无状态的。

We really discourage you from doing that, since contract tests should be stateless.

  • You want the same endpoint to return different results for the same request.

要创建有状态合约(或场景),需要在创建合约时使用适当的命名约定。约定要求包含一个后跟下划线的顺序号。无论使用 YAML 还是 Groovy,这都适用。以下列表显示了一个示例:

To create stateful contracts (or scenarios), you need to use the proper naming convention while creating your contracts. The convention requires including an order number followed by an underscore. This works regardless of whether you work with YAML or Groovy. The following listing shows an example:

my_contracts_dir\
  scenario1\
    1_login.groovy
    2_showCart.groovy
    3_logout.groovy

这样的树会导致 Spring Cloud Contract Verifier 生成名称为“scenario1”且存在以下三步的 WireMock 场景:

Such a tree causes Spring Cloud Contract Verifier to generate WireMock’s scenario with a name of scenario1 and the three following steps:

  1. login, marked as Started pointing to…​

  2. showCart, marked as Step1 pointing to…​

  3. logout, marked as Step2 (which closes the scenario).

您可以在 https://wiremock.org/docs/stateful-behaviour/ 找到有关 WireMock 场景的更多详细信息。

You can find more details about WireMock scenarios at https://wiremock.org/docs/stateful-behaviour/.