1. 概要

この簡単な記事では、消費者主導の契約の概念を見ていきます。

Pact ライブラリを使用して定義したコントラクトを通じて、外部RESTサービスとの統合をテストします。 その契約はクライアントが定義し、プロバイダーが取得してサービスの開発に使用できます。

また、クライアントアプリケーションとプロバイダーアプリケーションの両方の契約に基づいてテストを作成します。

2. 協定とは何ですか?

Pactを使用すると、特定のプロバイダー(HTTP RESTサービスの場合もあります)に対する消費者の期待をコントラクト(したがってライブラリの名前)の形式で定義できます。

Pactが提供するDSLを使用してこの契約を設定します。 定義すると、定義したコントラクトに基づいて作成されたモックサービスを使用して、コンシューマーとプロバイダー間の相互作用をテストできます。 また、モッククライアントを使用して、契約に対してサービスをテストします。

3. Mavenの依存関係

開始するには、Maven依存関係をpact-jvm-consumer-junit_2.11ライブラリに追加する必要があります。

<dependency>
    <groupId>au.com.dius</groupId>
    <artifactId>pact-jvm-consumer-junit_2.11</artifactId>
    <version>3.5.0</version>
    <scope>test</scope>
</dependency>

4. 契約の定義

Pact を使用してテストを作成する場合は、最初に、テストで使用する@Ruleを定義する必要があります。

@Rule
public PactProviderRuleMk2 mockProvider
  = new PactProviderRuleMk2("test_provider", "localhost", 8080, this);

サーバーモック(コントラクトから作成される)が開始されるプロバイダー名、ホスト、およびポートを渡します。

サービスが処理できる2つのHTTPメソッドのコントラクトを定義したとしましょう。

最初のメソッドは、2つのフィールドを持つJSONを返すGETリクエストです。 リクエストが成功すると、200HTTP応答コードとJSONのContent-Typeヘッダーが返されます。

Pactを使用してそのような契約を定義しましょう。

@Pactアノテーションを使用して、コントラクトが定義されているコンシューマー名を渡す必要があります。アノテーション付きメソッド内で、GETコントラクトを定義できます。

@Pact(consumer = "test_consumer")
public RequestResponsePact createPact(PactDslWithProvider builder) {
    Map<String, String> 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メソッドです。 クライアントがPOSTリクエストをパス/pact に適切なJSON本文で送信すると、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:を構成できます。

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <configuration>
        <systemPropertyVariables>
            <pact.rootDir>target/mypacts</pact.rootDir>
        </systemPropertyVariables>
    </configuration>
    ...
</plugin>

Mavenビルドは、test_consumer-test_provider.jsonというファイルをtarget/ mypacts フォルダーに生成します。このファイルには、要求と応答の構造が含まれています。

{
    "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. クライアントのテスト

コントラクトを定義したら、そのコントラクトに基づいて作成されるサービスとの相互作用をテストできます。 通常のJUnitテストを作成できますが、テストの最初に@PactVerificationアノテーションを付けることを忘れないでください。

GETリクエストのテストを書いてみましょう。

@Test
@PactVerification()
public void givenGet_whenSendRequest_shouldReturn200WithProperHeaderAndBody() {
 
    // when
    ResponseEntity<String> 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");
}

@PactVerificationアノテーションがHTTPサービスの開始を処理します。テストでは、GETリクエストを送信し、レスポンスがコントラクトに準拠していることを表明するだけで済みます。

POSTメソッド呼び出しのテストも追加しましょう。

HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
String jsonBody = "{\"name\": \"Michael\"}";

// when
ResponseEntity<String> postResponse = new RestTemplate()
  .exchange(
    mockProvider.getUrl() + "/create",
    HttpMethod.POST,
    new HttpEntity<>(jsonBody, httpHeaders), 
    String.class
);

//then
assertThat(postResponse.getStatusCode().value()).isEqualTo(201);

ご覧のとおり、POSTリクエストの応答コードは201に等しく、契約契約で定義されているとおりです。

@PactVerification()アノテーションを使用していたため、 Pact ライブラリは、テストケースの前に以前に定義されたコントラクトに基づいてWebサーバーを起動しています。

5.2. プロバイダーのテスト

契約検証の2番目のステップは、契約に基づいて模擬クライアントを使用してプロバイダーのテストを作成することです。

私たちのプロバイダーの実装は、TDD方式でこの契約によって推進されます。

この例では、Spring BootRESTAPIを使用します。

まず、JUnitテストを作成するために、pact-jvm-provider-junit_2.11依存関係を追加する必要があります。

<dependency>
    <groupId>au.com.dius</groupId>
    <artifactId>pact-jvm-provider-junit_2.11</artifactId>
    <version>3.5.0</version>
    <scope>test</scope>
</dependency>

これにより、 PactRunner を使用し、プロバイダー名とPactアーティファクトの場所を指定してJUnitテストを作成できます。

@RunWith(PactRunner.class)
@Provider("test_provider")
@PactFolder("pacts")
public class PactProviderTest {
    //...
}

この構成を機能させるには、test_consumer-test_provider.jsonファイルをRESTサービスプロジェクトのpactsフォルダーに配置する必要があります。

次に、コントラクト内の相互作用を検証するために使用するターゲットを定義し、テストを実行する前にSpring Bootアプリを起動します。

@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プロジェクトにあります。

6. 結論

このクイックチュートリアルでは、消費者主導の契約について説明しました。

Pactライブラリを使用して契約を作成しました。 コントラクトを定義すると、クライアントとサービスをコントラクトに対してテストし、仕様に準拠していることを確認できました。

これらすべての例とコードスニペットの実装は、 GitHubプロジェクトにあります。これはMavenプロジェクトであるため、そのままインポートして実行するのは簡単です。