開發您的第一個基於 Spring Cloud Contract 的應用程式
在生產者端
要開始使用 Spring Cloud Contract,您可以將 Spring Cloud Contract Verifier 依賴項和外掛新增到構建檔案中,如下例所示
以下清單顯示瞭如何新增外掛,該外掛應位於檔案的 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”新增為依賴項。這樣做會引入前面提到的依賴項以及
|
現在您可以將以 Groovy DSL 或 YAML 表示的 REST/ 訊息合同檔案新增到合同目錄中,該目錄由 contractsDslDir 屬性設定。預設情況下,它是 $rootDir/src/test/resources/contracts。請注意,檔名無關緊要。您可以在此目錄中根據您喜歡的任何命名方案組織合同。
對於 HTTP 存根,合同定義了針對給定請求(考慮到 HTTP 方法、URL、標頭、狀態碼等)應返回何種響應。以下示例顯示了 Groovy 和 YAML 中的 HTTP 存根合同
- groovy
-
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 訊息傳遞合同
- groovy
-
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. 下。
生成的測試可能有所不同,具體取決於您在外掛中設定的框架和測試型別。
在下一個清單中,您可以找到
-
MockMvc中 HTTP 合同的預設測試模式 -
帶有
JAXRS測試模式的 JAX-RS 客戶端 -
基於
WebTestClient的測試(在使用響應式、基於Web-Flux的應用程式時特別推薦)設定為WEBTESTCLIENT測試模式
| 您只需要其中一個測試框架。MockMvc 是預設的。要使用其他框架之一,請將其庫新增到您的類路徑。 |
以下清單顯示了所有框架的示例
- 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"); } - jaxrs
-
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"); } } - webtestclient
-
@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 | 如果金額過多,我們返回測試期望的 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
您現在可以合併更改,並將應用程式和存根工件釋出到線上倉庫中。
在消費者端
您可以在整合測試中使用 Spring Cloud Contract Stub Runner 來獲取一個執行中的 WireMock 例項或訊息路由,以模擬實際服務。
首先,按如下方式新增對 Spring Cloud Contract Stub Runner 的依賴項
您可以透過以下兩種方式將生產者端存根安裝到您的 Maven 倉庫中
-
透過檢出生產者端倉庫並新增合同,然後執行以下命令生成存根
$ cd local-http-server-repo $ ./mvnw clean install -DskipTests測試被跳過,因為生產者端合同實現尚未就位,因此自動生成的合同測試會失敗。 -
從遠端倉庫獲取現有的生產者服務存根。為此,將存根工件 ID 和工件倉庫 URL 作為
Spring Cloud Contract Stub Runner屬性傳遞,如下例所示
現在您可以使用 @AutoConfigureStubRunner 註解您的測試類。在註解中,為 Spring Cloud Contract Stub Runner 提供 group-id 和 artifact-id,以便為您執行協作者的存根,如下例所示
@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 響應或訊息的存根版本。您可以在構建日誌中看到與以下內容類似的條目
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}]