Developing Your First Spring Cloud Contract-based Application
此简要指南介绍如何使用 Spring Cloud Contract。它包含以下主题:
您可在 here 中找到更简洁的教程。 为了这个示例,“存根存储”是 Nexus/Artifactory。 以下 UML 图表显示了 Spring Cloud Contract 的各部分之间的关系:
"API Producer"->"API Producer": add Spring Cloud \nContract (SCC) plugin "API Producer"->"API Producer": add SCC Verifier dependency "API Producer"->"API Producer": define contracts "API Producer"->"Build": run build "Build"->"SCC Plugin": generate \ntests, stubs and stubs \nartifact (e.g. stubs-jar) "Build"->"Stub Storage": upload contracts \nand stubs and the project arifact "Build"->"API Producer": Build successful "API Consumer"->"API Consumer": add SCC Stub Runner \ndependency "API Consumer"->"API Consumer": write a SCC Stub Runner \nbased contract test "SCC Stub Runner"->"Stub Storage": test asks for [API Producer] stubs "Stub Storage"->"SCC Stub Runner": fetch the [API Producer] stubs "SCC Stub Runner"->"SCC Stub Runner": run in memory\n HTTP server stubs "API Consumer"->"SCC Stub Runner": send a request \nto the HTTP server stub "SCC Stub Runner"->"API Consumer": communication is correct
On the Producer Side
要开始使用 Spring Cloud Contract
,您可以向构建文件添加 Spring Cloud Contract Verifier 依赖项和插件,如下例所示:
Unresolved directive in first-application.adoc - include::{samples_path}/standalone/dsl/http-server/pom.xml[]
以下清单显示了如何添加插件,该插件应位于文件的 build/plugin 部分:
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<version>${spring-cloud-contract.version}</version>
<extensions>true</extensions>
</plugin>
最简单的入门方法是转到 the Spring Initializr 并将 “Web” 和 “Contract Verifier” 添加为依赖项。这样做会拉入前面提到的依赖项以及 |
现在,您可以将使用 Groovy DSL 或 YAML 编写的 REST/
消息传递合约的文件添加到合约目录,该目录由 contractsDslDir
属性设置。默认情况下,它是 $rootDir/src/test/resources/contracts
。请注意,文件名无关紧要。您可以使用您喜欢的任何命名方案在此目录中组织合约。
对于 HTTP 存根,合约定义了应为给定请求返回哪种类型的响应(考虑 HTTP 方法、URL、标头、状态代码等)。以下示例显示了 Groovy 和 YAML 中的 HTTP 存根合约:
-
groovy
-
yaml
package contracts
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')
}
}
}
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 消息传递合约:
-
groovy
-
yaml
Unresolved directive in first-application.adoc - include::{verifier_root_path}/src/test/groovy/org/springframework/cloud/contract/verifier/builder/MessagingMethodBodyBuilderSpec.groovy[]
Unresolved directive in first-application.adoc - include::{verifier_root_path}/src/test/resources/yml/contract_message_scenario1.yml[]
运行 ./mvnw clean install
会自动生成验证应用程序与添加的合约兼容性的测试。默认情况下,生成的测试在 org.springframework.cloud.contract.verifier.tests.
下。
生成的测试可能有所不同,具体取决于您在插件中设置的框架和测试类型。
在下一个列表中,您可以找到:
-
MockMvc
中 HTTP 契约的默认测试模式 -
具有
JAXRS
测试模式的 JAX-RS 客户端 -
具有
WEBTESTCLIENT
测试模式的基于WebTestClient
的测试(在使用基于 Reactivity 的Web-Flux
应用程序时特别推荐这种方法)
你只需要其中一个测试框架。MockMvc 是默认设置。要使用其他框架之一,请将它的库添加到你的 classpath。 |
以下列表显示了所有框架的示例:
-
mockmvc
-
jaxrs
-
webtestclient
@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", "bob@email.com")
.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");
}
}
@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 请求或消息的正确实现。此外,您必须为自动生成的测试向项目添加一个基本测试类。所有自动生成的测试都将扩展此类,该类应包含运行它们所需的所有必要设置信息(例如,RestAssuredMockMvc
控制器设置或消息传递测试设置)。
以下示例来自 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 | baseClassForTests 元素让你可以选择你的基本测试类。它必须是 spring-cloud-contract-maven-plugin 内 configuration 元素的一个子元素。 |
以下示例显示了一个最小的(但可以正常工作的)基本测试类:
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());
}
}
这个最小的类确实就是让您的测试正常工作所需的一切。它用作自动生成的测试附加到的起始点。
现在我们可以继续实现。为此,我们首先需要一个数据类,然后在控制器中使用它。以下列表显示了数据类:
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;
}
}
前面的类提供了一个对象,我们可以在其中存储参数。因为协议中的客户端 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 | 如果数量过大,我们返回 thetest 预期的 JSON(在此使用一个简单的字符串创建)。 |
4 | 如果我们有一个测试来捕获金额允许的数量,我们可以将其与该输出匹配。 |
FraudController
非常简单。您可以执行更多操作,包括记录、验证客户端 ID 等。
一旦实现和测试基础类就位,测试就会通过,应用程序和存根制品都会在本地 Maven 存储库中构建并安装。如下例所示,有关将存根 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
现在您可以合并更改并在在线存储库中发布应用程序和存根制品。
On the Consumer Side
您可以在集成测试中使用 Spring Cloud Contract Stub Runner 获得运行 WireMock 实例或模拟实际服务的消息传递路由。
首先,请按如下方式向“Spring Cloud Contract Stub Runner”添加依赖项:
Unresolved directive in first-application.adoc - include::{samples_path}/standalone/dsl/http-client/pom.xml[]
您可以采用两种方式之一在您的 Maven 存储库中安装 Producer 端存根:
-
通过签出 Producer 侧代码库并添加合约,并通过运行以下命令生成存根:[source, bash]
$ cd local-http-server-repo $ ./mvnw clean install -DskipTests
这些测试将予以跳过,因为 Producer 端契约实现尚未到位,因此自动生成的契约测试将会失败。 |
-
通过从远程代码库中获取现有的生产者服务存根来实现。为此,请将存根工件 ID 和工件代码库 URL 作为
Spring Cloud Contract Stub Runner
属性传递,如下面的示例所示:[source, yaml]
Unresolved directive in first-application.adoc - include::{samples_path}/standalone/dsl/http-client/src/test/resources/application-test-repo.yaml[]
现在,您可以使用 @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 {
. . .
}
在线储存库中下载存根时,请使用 |
在您的集成测试中,您可以接收由合作者服务预期发出的 HTTP 响应或消息的存根版本。您可以在构建日志中看到类似于以下内容的条目:
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}]