此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 spring-cloud-contract 4.1.5! |
开发您的第一个基于 Contract 的 Spring Cloud 应用程序
在生产者端
要开始使用 ,您可以添加 Spring Cloud Contract Verifier
dependency 和 plugin 添加到您的构建文件中,如下例所示:Spring Cloud Contract
下面的清单显示了如何添加插件,它应该放在 build/plugins 中 部分:
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<version>${spring-cloud-contract.version}</version>
<extensions>true</extensions>
</plugin>
最简单的入门方法是转到 Spring Initializr 并添加 “Web” 和 “Contract Verifier” 作为依赖项。这样做会引入以前的
提到的依赖项以及文件中需要的所有其他内容(除了
设置基测试类,我们将在本节后面介绍)。下图
显示了要在 Spring Initializr 中使用的设置: |
现在,您可以使用消息传递协定添加文件
以 Groovy DSL 或 YAML 表示到 contracts 目录,该目录由属性设置。默认情况下,它是 。
请注意,文件名无关紧要。您可以在此
目录中。REST/
contractsDslDir
$rootDir/src/test/resources/contracts
对于 HTTP 存根,协定定义了应为 给定的请求(考虑 HTTP 方法、URL、标头、状态代码等 on) 的 S S以下示例显示了 Groovy 和 YAML 中的 HTTP 存根 Contract:
- 槽的
-
org.springframework.cloud.contract.spec.Contract.make { request { method 'PUT' url '/fraudcheck' body([ "client.id": $(regex('[0-9]{10}')), loanAmount: 99999 ]) headers { contentType('application/json') } } response { status OK() body([ fraudCheckStatus: "FRAUD", "rejection.reason": "Amount too high" ]) headers { contentType('application/json') } } }
- YAML
-
request: method: PUT url: /fraudcheck body: "client.id": 1234567890 loanAmount: 99999 headers: Content-Type: application/json matchers: body: - path: $.['client.id'] type: by_regex value: "[0-9]{10}" response: status: 200 body: fraudCheckStatus: "FRAUD" "rejection.reason": "Amount too high" headers: Content-Type: application/json;charset=UTF-8
如果需要使用消息传递,您可以定义:
-
输入和输出消息(考虑到它的位置 、邮件正文和标头)。
-
收到消息后应调用的方法。
-
调用时应触发消息的方法。
-
以下示例显示了 Camel 消息传递协定:
- 槽的
-
def contractDsl = Contract.make { name "foo" label 'some_label' input { triggeredBy('bookReturnedTriggered()') } outputMessage { sentTo('activemq:output') body('''{ "bookName" : "foo" }''') headers { header('BOOK-NAME', 'foo') messagingContentType(applicationJson()) } } }
- YAML
-
label: some_label input: triggeredBy: bookReturnedTriggered outputMessage: sentTo: activemq:output body: bookName: foo headers: BOOK-NAME: foo contentType: application/json
运行会自动生成验证应用程序的测试
遵守添加的合同。默认情况下,生成的测试位于 ../mvnw clean install
org.springframework.cloud.contract.verifier.tests.
生成的测试可能会有所不同,具体取决于您设置的框架和测试类型 在你的插件中。
在下一个列表中,您可以找到:
-
HTTP Contract 的默认测试模式
MockMvc
-
具有测试模式的 JAX-RS 客户端
JAXRS
-
基于 的测试(在使用 基于响应式应用程序)设置的 test 模式
WebTestClient
Web-Flux
WEBTESTCLIENT
您只需要其中一个测试框架。MockMvc 是默认值。要使用一个 ,将其库添加到您的 Classpath 中。 |
下面的清单显示了所有框架的示例:
- MOCKMVC
-
@Test public void validate_shouldMarkClientAsFraud() throws Exception { // given: MockMvcRequestSpecification request = given() .header("Content-Type", "application/vnd.fraud.v1+json") .body("{\"client.id\":\"1234567890\",\"loanAmount\":99999}"); // when: ResponseOptions response = given().spec(request) .put("/fraudcheck"); // then: assertThat(response.statusCode()).isEqualTo(200); assertThat(response.header("Content-Type")).matches("application/vnd.fraud.v1.json.*"); // and: DocumentContext parsedJson = JsonPath.parse(response.getBody().asString()); assertThatJson(parsedJson).field("['fraudCheckStatus']").matches("[A-Z]{5}"); assertThatJson(parsedJson).field("['rejection.reason']").isEqualTo("Amount too high"); }
- 贾克斯
-
public class FooTest { WebTarget webTarget; @Test public void validate_() throws Exception { // when: Response response = webTarget .path("/users") .queryParam("limit", "10") .queryParam("offset", "20") .queryParam("filter", "email") .queryParam("sort", "name") .queryParam("search", "55") .queryParam("age", "99") .queryParam("name", "Denis.Stepanov") .queryParam("email", "[email protected]") .request() .build("GET") .invoke(); String responseAsString = response.readEntity(String.class); // then: assertThat(response.getStatus()).isEqualTo(200); // and: DocumentContext parsedJson = JsonPath.parse(responseAsString); assertThatJson(parsedJson).field("['property1']").isEqualTo("a"); } }
- webtest客户端
-
@Test public void validate_shouldRejectABeerIfTooYoung() throws Exception { // given: WebTestClientRequestSpecification request = given() .header("Content-Type", "application/json") .body("{\"age\":10}"); // when: WebTestClientResponse response = given().spec(request) .post("/check"); // then: assertThat(response.statusCode()).isEqualTo(200); assertThat(response.header("Content-Type")).matches("application/json.*"); // and: DocumentContext parsedJson = JsonPath.parse(response.getBody().asString()); assertThatJson(parsedJson).field("['status']").isEqualTo("NOT_OK"); }
由于合同中描述的功能尚未实现 存在,则测试失败。
要使它们通过,您必须添加处理 HTTP 的正确实现
请求或消息。此外,您必须为自动生成的
tests 添加到项目中。这个类由所有自动生成的测试扩展,并且应该
包含运行它们所需的所有设置必要信息(例如,控制器设置或消息传递测试设置)。RestAssuredMockMvc
以下示例 from 显示了如何指定 base test 类:pom.xml
<build>
<plugins>
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<version>2.1.2.RELEASE</version>
<extensions>true</extensions>
<configuration>
<baseClassForTests>com.example.contractTest.BaseTestClass</baseClassForTests> (1)
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
1 | 该元素允许您指定基本测试类。它必须是 child
的元素中。baseClassForTests configuration spring-cloud-contract-maven-plugin |
以下示例显示了一个最小(但功能正常)的基测试类:
package com.example.contractTest;
import org.junit.Before;
import io.restassured.module.mockmvc.RestAssuredMockMvc;
public class BaseTestClass {
@Before
public void setup() {
RestAssuredMockMvc.standaloneSetup(new FraudController());
}
}
这个最小的类确实是使测试正常工作所需的全部。它充当 自动生成的测试附加到的起始位置。
现在我们可以继续实现。为此,我们首先需要一个 data 类,我们 然后在我们的控制器中使用。下面的清单显示了 data 类:
package com.example.Test;
import com.fasterxml.jackson.annotation.JsonProperty;
public class LoanRequest {
@JsonProperty("client.id")
private String clientId;
private Long loanAmount;
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public Long getLoanAmount() {
return loanAmount;
}
public void setLoanRequestAmount(Long loanAmount) {
this.loanAmount = loanAmount;
}
}
前面的类提供了一个对象,我们可以在其中存储参数。因为
client ID 中的调用,我们需要使用参数将其映射到字段。client.id
@JsonProperty("client.id")
clientId
现在我们可以转到控制器,下面的清单显示了这一点:
package com.example.docTest;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class FraudController {
@PutMapping(value = "/fraudcheck", consumes="application/json", produces="application/json")
public String check(@RequestBody LoanRequest loanRequest) { (1)
if (loanRequest.getLoanAmount() > 10000) { (2)
return "{fraudCheckStatus: FRAUD, rejection.reason: Amount too high}"; (3)
} else {
return "{fraudCheckStatus: OK, acceptance.reason: Amount OK}"; (4)
}
}
}
1 | 我们将传入的参数映射到一个对象。LoanRequest |
2 | 我们检查请求的贷款金额,看看是否太多。 |
3 | 如果太多,我们返回 JSON(在此处使用简单字符串创建),其中 test 期望。 |
4 | 如果我们有一个测试要捕获的数量何时允许,我们可以将其与此输出匹配。 |
事情就这么简单。您可以执行更多操作,包括
logging、验证客户端 ID 等。FraudController
一旦实现和测试基类就位,测试就会通过,并且 应用程序和存根工件在本地 Maven 存储库中构建和安装。 有关将 stubs jar 安装到本地存储库的信息显示在日志中,如 以下示例显示:
[INFO] --- spring-cloud-contract-maven-plugin:1.0.0.BUILD-SNAPSHOT:generateStubs (default-generateStubs) @ http-server ---
[INFO] Building jar: /some/path/http-server/target/http-server-0.0.1-SNAPSHOT-stubs.jar
[INFO]
[INFO] --- maven-jar-plugin:2.6:jar (default-jar) @ http-server ---
[INFO] Building jar: /some/path/http-server/target/http-server-0.0.1-SNAPSHOT.jar
[INFO]
[INFO] --- spring-boot-maven-plugin:1.5.5.BUILD-SNAPSHOT:repackage (default) @ http-server ---
[INFO]
[INFO] --- maven-install-plugin:2.5.2:install (default-install) @ http-server ---
[INFO] Installing /some/path/http-server/target/http-server-0.0.1-SNAPSHOT.jar to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT.jar
[INFO] Installing /some/path/http-server/pom.xml to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT.pom
[INFO] Installing /some/path/http-server/target/http-server-0.0.1-SNAPSHOT-stubs.jar to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar
现在,您可以合并更改并发布应用程序和存根工件 在联机存储库中。
在消费者方面
您可以在集成测试中使用 Spring Cloud Contract Stub Runner 来运行 模拟实际服务的 WireMock 实例或消息传送路由。
首先,将依赖项添加到 ,如下所示:Spring Cloud Contract Stub Runner
您可以通过以下两种方式将生产者端存根安装在 Maven 存储库中 方式:
-
通过签出 Producer 端存储库并添加合约并生成 stubs 来运行以下命令:
$ cd local-http-server-repo $ ./mvnw clean install -DskipTests
跳过测试,因为 Producer 端合约实现尚未 就位,因此自动生成的合约测试将失败。 -
通过从远程存储库获取现有的生产者服务存根。为此, 将存根构件 ID 和构件存储库 URL 作为属性传递,如下例所示:
Spring Cloud Contract Stub Runner
现在,您可以使用 .在注释中,
提供 和 for 运行
协作者的存根,如下例所示:@AutoConfigureStubRunner
group-id
artifact-id
Spring Cloud Contract Stub Runner
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment=WebEnvironment.NONE)
@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:6565"},
stubsMode = StubRunnerProperties.StubsMode.LOCAL)
public class LoanApplicationServiceTests {
. . .
}
从在线存储库下载存根时和离线工作时使用。REMOTE stubsMode LOCAL |
在集成测试中,您可以接收 HTTP 响应或消息的存根版本 ,这些请求应由 Collaborator 服务发出。您可以看到类似的条目 添加到构建日志中的以下内容:
2016-07-19 14:22:25.403 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Desired version is + - will try to resolve the latest version
2016-07-19 14:22:25.438 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Resolved version is 0.0.1-SNAPSHOT
2016-07-19 14:22:25.439 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Resolving artifact com.example:http-server:jar:stubs:0.0.1-SNAPSHOT using remote repositories []
2016-07-19 14:22:25.451 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Resolved artifact com.example:http-server:jar:stubs:0.0.1-SNAPSHOT to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar
2016-07-19 14:22:25.465 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Unpacking stub from JAR [URI: file:/path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar]
2016-07-19 14:22:25.475 INFO 41050 --- [ main] o.s.c.c.stubrunner.AetherStubDownloader : Unpacked file to [/var/folders/0p/xwq47sq106x1_g3dtv6qfm940000gq/T/contracts100276532569594265]
2016-07-19 14:22:27.737 INFO 41050 --- [ main] o.s.c.c.stubrunner.StubRunnerExecutor : All stubs are now running RunningStubs [namesAndPorts={com.example:http-server:0.0.1-SNAPSHOT:stubs=8080}]