協定による消費者主導の契約
1. 概要
この簡単な記事では、消費者主導の契約の概念について説明します。
Pactライブラリを使用して定義したコントラクトを通じて、外部RESTサービスとの統合をテストします。 That contract can be defined by the client, then picked up by the provider and used for development of its services.
また、クライアントアプリケーションとプロバイダーアプリケーションの両方の契約に基づいてテストを作成します。
2. Pactとは何ですか?
Using Pact, we can define consumer expectations for a given provider (that can be an HTTP REST service) in the form of a contract(したがってライブラリの名前)。
Pactが提供するDSLを使用してこの契約を設定します。 定義したら、定義済みのコントラクトに基づいて作成された模擬サービスを使用して、コンシューマーとプロバイダー間の対話をテストできます。 また、モッククライアントを使用して、契約に対してサービスをテストします。
3. メーベン依存
開始するには、Maven依存関係をpact-jvm-consumer-junit_2.11ライブラリに追加する必要があります。
au.com.dius
pact-jvm-consumer-junit_2.11
3.5.0
test
4. 契約の定義
Pactを使用してテストを作成する場合、最初に、テストで使用される@Ruleを定義する必要があります。
@Rule
public PactProviderRuleMk2 mockProvider
= new PactProviderRuleMk2("test_provider", "localhost", 8080, this);
サーバーモック(契約から作成されたもの)が開始されるプロバイダー名、ホスト、およびポートを渡します。
サービスが、処理できる2つのHTTPメソッドのコントラクトを定義したとします。
最初のメソッドは、2つのフィールドを持つJSONを返すGETリクエストです。 リクエストが成功すると、200 HTTP応答コードとJSONのContent-Typeヘッダーが返されます。
Pactを使用してそのようなコントラクトを定義しましょう。
We need to use the @Pact annotation and pass the consumer name for which the contract is defined.アノテーション付きメソッド内で、GETコントラクトを定義できます。
@Pact(consumer = "test_consumer")
public RequestResponsePact createPact(PactDslWithProvider builder) {
Map headers = new HashMap<>();
headers.put("Content-Type", "application/json");
return builder
.given("test GET")
.uponReceiving("GET REQUEST")
.path("/pact")
.method("GET")
.willRespondWith()
.status(200)
.headers(headers)
.body("{\"condition\": true, \"name\": \"tom\"}")
(...)
}
Pact DSLを使用して、特定のGET要求に対して、特定のヘッダーと本文を含む200応答を返すように定義します。
コントラクトの2番目の部分はPOSTメソッドです。 クライアントが適切なJSON本文を使用してパス/pactにPOSTリクエストを送信すると、201HTTP応答コードが返されます。
そのような契約をPact:で定義しましょう
(...)
.given("test POST")
.uponReceiving("POST REQUEST")
.method("POST")
.headers(headers)
.body("{\"name\": \"Michael\"}")
.path("/pact")
.willRespondWith()
.status(201)
.toPact();
RequestResponsePactのインスタンスを返すには、コントラクトの最後にtoPact()メソッドを呼び出す必要があることに注意してください。
4.1. 結果として生じる協定アーティファクト
デフォルトでは、Pactファイルはtarget/pactsフォルダーに生成されます。 このパスをカスタマイズするために、maven-surefire-plugin:を構成できます
org.apache.maven.plugins
maven-surefire-plugin
target/mypacts
...
Mavenビルドは、リクエストとレスポンスの構造を含むtarget/mypactsフォルダーにtest_consumer-test_provider.jsonというファイルを生成します。
{
"provider": {
"name": "test_provider"
},
"consumer": {
"name": "test_consumer"
},
"interactions": [
{
"description": "GET REQUEST",
"request": {
"method": "GET",
"path": "/"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"body": {
"condition": true,
"name": "tom"
}
},
"providerStates": [
{
"name": "test GET"
}
]
},
{
"description": "POST REQUEST",
...
}
],
"metadata": {
"pact-specification": {
"version": "3.0.0"
},
"pact-jvm": {
"version": "3.5.0"
}
}
}
5. コントラクトを使用したクライアントとプロバイダーのテスト
契約ができたので、クライアントとプロバイダーの両方に対して契約をテストするために使用できます。
これらの各テストでは、契約に基づいた対応物のモックを使用します。
-
クライアントはモックプロバイダーを使用します
-
プロバイダーは模擬クライアントを使用します
事実上、テストは契約に対して行われます。
5.1. クライアントのテスト
契約を定義したら、その契約に基づいて作成されるサービスとの相互作用をテストできます。 We can create normal JUnit test but we need to remember to put the @PactVerification annotation at the beginning of the test.
GETリクエストのテストを書いてみましょう。
@Test
@PactVerification()
public void givenGet_whenSendRequest_shouldReturn200WithProperHeaderAndBody() {
// when
ResponseEntity response = new RestTemplate()
.getForEntity(mockProvider.getUrl() + "/pact", String.class);
// then
assertThat(response.getStatusCode().value()).isEqualTo(200);
assertThat(response.getHeaders().get("Content-Type").contains("application/json")).isTrue();
assertThat(response.getBody()).contains("condition", "true", "name", "tom");
}
The @PactVerification annotation takes care of starting the HTTP service.テストでは、GET要求を送信し、応答がコントラクトに準拠していることを表明するだけで済みます。
POSTメソッド呼び出しのテストも追加しましょう。
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
String jsonBody = "{\"name\": \"Michael\"}";
// when
ResponseEntity postResponse = new RestTemplate()
.exchange(
mockProvider.getUrl() + "/create",
HttpMethod.POST,
new HttpEntity<>(jsonBody, httpHeaders),
String.class
);
//then
assertThat(postResponse.getStatusCode().value()).isEqualTo(201);
ご覧のとおり、POSTリクエストの応答コードは201に等しく、Pactコントラクトで定義されているとおりです。
@PactVerification()アノテーションを使用していたため、Pactライブラリは、テストケースの前に以前に定義されたコントラクトに基づいてWebサーバーを起動しています。
5.2. プロバイダーのテスト
契約検証の2番目のステップは、契約に基づいて模擬クライアントを使用してプロバイダーのテストを作成することです。
プロバイダーの実装は、TDD方式のこの契約によって推進されます。
この例では、Spring Boot RESTAPIを使用します。
まず、JUnitテストを作成するために、pact-jvm-provider-junit_2.11の依存関係を追加する必要があります。
au.com.dius
pact-jvm-provider-junit_2.11
3.5.0
test
これにより、PactRunnerを使用し、プロバイダー名とPactアーティファクトの場所を指定してJUnitテストを作成できます。
@RunWith(PactRunner.class)
@Provider("test_provider")
@PactFolder("pacts")
public class PactProviderTest {
//...
}
この構成を機能させるには、test_consumer-test_provider.jsonファイルをRESTサービスプロジェクトのpactsフォルダーに配置する必要があります。
次に、コントラクト内の相互作用を検証するために使用するターゲットを定義し、テストを実行する前にSpringBootアプリを起動します。
@TestTarget
public final Target target = new HttpTarget("http", "localhost", 8082, "/spring-rest");
private static ConfigurableWebApplicationContext application;
@BeforeClass
public static void start() {
application = (ConfigurableWebApplicationContext)
SpringApplication.run(MainApplication.class);
}
最後に、テストする契約の状態を指定します。
@State("test GET")
public void toGetState() { }
@State("test POST")
public void toPostState() { }
このJUnitクラスを実行すると、2つのGETおよびPOST要求に対して2つのテストが実行されます。 ログを見てみましょう:
Verifying a pact between test_consumer and test_provider
Given test GET
GET REQUEST
returns a response which
has status code 200 (OK)
includes headers
"Content-Type" with value "application/json" (OK)
has a matching body (OK)
Verifying a pact between test_consumer and test_provider
Given test POST
POST REQUEST
returns a response which
has status code 201 (OK)
has a matching body (OK)
ここにはRESTサービスを作成するためのコードが含まれていないことに注意してください。 完全なサービスとテストはGitHub projectにあります。
6. 結論
このクイックチュートリアルでは、コンシューマドリブンコントラクトについて説明しました。
Pactライブラリを使用してコントラクトを作成しました。 契約を定義したら、契約に対してクライアントとサービスをテストし、仕様に準拠していると断言することができました。
これらすべての例とコードスニペットの実装は、GitHub projectにあります。これはMavenプロジェクトであるため、そのままインポートして実行するのは簡単です。