初めての Spring Cloud 契約ベースのアプリケーションの開発
プロデューサー側
Spring Cloud Contract
の使用を開始するには、次の例に示すように、Spring Cloud Contract Verifier の依存関係とプラグインをビルドファイルに追加します。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-verifier</artifactId>
<scope>test</scope>
</dependency>
次のリストは、プラグインを追加する方法を示しています。プラグインは、ファイルの 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')
}
}
}
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())
}
}
}
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
テストモードで設定されたWebTestClient
ベースのテスト (これは、リアクティブなWeb-Flux
ベースのアプリケーションを使用する場合に特に推奨されます)
これらのテストフレームワークのうち 1 つだけが必要です。MockMvc がデフォルトです。他のフレームワークのいずれかを使用するには、そのライブラリをクラスパスに追加します。 |
次のリストは、すべてのフレームワークのサンプルを示しています。
モック MVC
ジャックス
Web テストクライアント
@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");
}
}
@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 スタブランナーを使用して、実際のサービスをシミュレートする実行中の WireMock インスタンスまたはメッセージングルートを取得できます。
まず、次のように依存関係を Spring Cloud Contract Stub Runner
に追加します。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
<scope>test</scope>
</dependency>
Maven リポジトリにインストールされているプロデューサー側のスタブは、次の 2 つの方法のいずれかで取得できます。
Producer 側のリポジトリをチェックアウトし、次のコマンドを実行して契約を追加し、スタブを生成します。
$ cd local-http-server-repo $ ./mvnw clean install -DskipTests
プロデューサー側の契約実装がまだ整っていないため、テストはスキップされ、自動生成された契約 テストは失敗します。 リモートリポジトリから既存のプロデューサーサービススタブを取得します。これを行うには、次の例に示すように、スタブアーティファクト ID とアーティファクトリポジトリ URL を
Spring Cloud Contract Stub Runner
プロパティとして渡します。stubrunner: ids: 'com.example:http-server-dsl:+:stubs:8080' repositoryRoot: https://repo.spring.io/libs-snapshot
これで、テストクラスに @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}]