APIをモック化する(Wiremock)

WireMock

公式HP

依存関係を追加する

build.gradleに以下の記述を追加する。

dependencies {
    testImplementation 'com.github.tomakehurst:wiremock-jre8:2.31.0'
}

Junit5で使用する

Junit5でWireMockを使用する方法として、declarativeprogrammaticの2つの操作モードをサポートしている。

  • declarative:アノテーションで簡単に設定が可能だが、限られた設定しかできないモード
  • programmatic:設定をプログラムで記述する、様々な設定が可能なモード。

declarativeモードでの使用方法

テストクラスにアノテーション@WireMockTestを付与する。
これにより、テスト実行時に単一のWireMockサーバーが起動する。デフォルトでランダムポート、HTTPとなる。

@WireMockTest
class SampleTest {
}

実行中のポート番号、ベースURLなどを取得するには、WireMockRuntimeInfoをテストメソッドの引数に追加する。

@WireMockTest
class SampleTest {

    @Test
    void test(WireMockRuntimeInfo info) {
        String baseUrl = info.getHttpsBaseUrl();
        int port = info.getHttpPort();
    }

}

上記の例では、WireMock サーバは、最初のテストメソッドの前に起動し、最後のテストメソッドが終了した後に停止される。
スタブのマッピングやリクエストは、各テストメソッドの前にリセットされる。

ポート番号を固定化する場合は、アノテーションの属性にhttpPortを追加する。

@WireMockTest(httpPort = 8080)
class SampleTest {
}

HTTPSを有効にするには、アノテーションの属性にhttpsEnabledを追加する。この場合ランダムポートとなる。

@WireMockTest(httpsEnabled = true)
class SampleTest {
}

HTTPSでかつポートを固定する場合はアノテーションの属性にhttpPorthttpsEnabledを両方追加する。

@WireMockTest(httpsEnabled = true, httpsPort = 8080)
class SampleTest {
}

programmaticモードでの使用方法

@RegisterExtensionアノテーションを付与したWireMockExtensionを複数定義することで、任意の数のWireMockインスタンスを実行することができ、設定をコントロールすることができる。
実行中のポート番号、ベースURLなどを取得するには、getRuntimeInfo()メソッドを実行する。

class SampleTest {

    @RegisterExtension
    static WireMockExtension wm1 = WireMockExtension.newInstance()
            .options(WireMockConfiguration.wireMockConfig().httpDisabled(false).port(3000))
            .build();

    @RegisterExtension
    static WireMockExtension wm2 = WireMockExtension.newInstance()
            .options(WireMockConfiguration.wireMockConfig()
                    .dynamicPort()
                    .extensions(new ResponseTemplateTransformer(true)))
            .build();

    @Test
    void test() {
        WireMockRuntimeInfo wm1RuntimeInfo = wm1.getRuntimeInfo();
        int httpsPort = wm1RuntimeInfo.getHttpsPort();
    }
}

上記のように、WireMockExtensionをstaticで定義した場合、各WireMockサーバは最初のテストメソッドの前に起動し、最後のテストメソッドが終了した後に停止され、各テストメソッドの前にresetが呼び出される(declarativeモードと同じ)。

WireMockExtension定義時にstaticを付与しない場合は各テストメソッドの前に生成・起動し、テストメソッドの終了後に停止する。


スタブを定義する

特定のHTTPメソッド、URI、ヘッダーの時のふるまいを定義することができる。
スタブを記述する際には、com.github.tomakehurst.wiremock.client.WireMockクラスのメソッドを使用する。
以下の説明で使用するメソッドは、用意されているメソッドの一部なので詳しくは公式のページのDoc参照のこと。

スタブの定義をする際にはstubFor(MappingBuilder)というメソッドを使用する。 stubFor(MappingBuilder)の引数の中で具体的なリクエストやレスポンスを定義していく。
以下に具体的なスタブの定義方法を説明する。

1. HTTPメソッドを指定する

WireMockがサポートしているHTTPメソッドは get、post、put、delete、head、trace、optionsの7つ。
任意のHTTPメソッドで良い場合は、anyを指定する。

mock.stubFor(WireMock.get());

2. HTTPメソッドの引数にURLを指定する。

この時、urlEqualTo(String)を使用した場合は、引数のURLと完全一致した際にこのスタブが実行される。
この他にuriMatching(String)というメソッドもあり、このメソッドでは引数に正規表現を与え、マッチするURLの場合にこのスタブが実行される。
任意のURLで良い場合はanyURL()を指定する。

mock.stubFor(WireMock.get(WireMock.urlEqualTo("/some/thing"))));

3. ヘッダー等を追加する

HTTPメソッドに続けてwith*()メソッドを使用することで、リクエストにヘッダーなどを追加することができる。
URLの場合と同様に、期待値を指定する際に完全一致(equalTo())や正規表現(matching())、部分一致(containing())など様々なマッチングを使用することができる。詳しい説明はここを参照。

mock.stubFor(WireMock.get(WireMock.urlEqualTo("/some/thing"))
            .withQueryParam("name", WireMock.containing("Bob"))
            .withHeader("Content-Type", WireMock.equalTo("application/json")));

4. レスポンスの設定をする

リクエストの設定に続けてwillReturn()メソッドを使用することで、レスポンスの定義をすることができる。

mock.stubFor(WireMock.get(WireMock.urlEqualTo("/some/thing"))
            .withQueryParam("name", WireMock.containing("Bob"))
            .withHeader("Content-Type", WireMock.equalTo("application/json"))
            .willReturn(WireMock.aResponse()
                .withHeader("Content-Type", "text/plain")
                .withBody("Hello world!")));

以下にスタブの定義の記述例を示す。

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
import com.github.tomakehurst.wiremock.junit5.WireMockExtension;

class SampleTest {

    @RegisterExtension
    static WireMockExtension mock = WireMockExtension.newInstance()
            .options(WireMockConfiguration.wireMockConfig().httpDisabled(false).port(3000))
            .build();

    @Test
    void test() {
        mock.stubFor(WireMock.get(WireMock.urlEqualTo("/some/thing?name=Bob"))
                .withQueryParam("name", WireMock.containing("Bob"))
                .withHeader("Content-Type", WireMock.equalTo("application/json"))
                .willReturn(WireMock.aResponse()
                        .withHeader("Content-Type", "application/json")
                        .withBody("Hello world!")));
    }

}

マッピングファイルを用意してスタブを登録することもできる。
src/test/resources/mappingsフォルダを作成し、その配下にjsonファイルを作成し、スタブの定義を記述する。
マッピングファイルででのスタブ定義例は以下の通り。

{
    "request": {
        "method": "GET",
        "url": "/some/thing?name=Bob",
        "headers" : {
            "Content-Type": {
                "equalTo" : "application/json"
            }
        },
        "queryParameters" : {
            "name" : {
                "containing" : "Bob"
            }
        }
    },
    "response": {
        "status": 200,
        "body":  "Hello world!",
        "headers": {
            "Content-Type": "application/json"
        }
    }
}

スタブの優先度を設定する

複数のスタブを宣言している場合、あるURLを指定した時にマッチングするスタブが複数存在する場合がある。
デフォルトでは、より新しい(より後で定義した)スタブが優先される。
自分で優先度の設定を行いたい場合は、.atPriority(int)メソッドを使用して優先度を設定する。

class SampleTest {

    @RegisterExtension
    static WireMockExtension mock = WireMockExtension.newInstance()
            .options(WireMockConfiguration.wireMockConfig().httpDisabled(false).port(3000))
            .build();

    @Test
    void test() {
        mock.stubFor(WireMock.get(WireMock.urlEqualTo("/api/some/thing"))
                // ↓ 追加
                .atPriority(1)
                // ↑ 追加
                .willReturn(WireMock.aResponse()
                        .withHeader("Content-Type", "text/plain")
                        .withBody("Hello world!")));

        mock.stubFor(WireMock.get("/api/.*")
                // ↓ 追加
                .atPriority(5)
                // ↑ 追加
                .willReturn(WireMock.ok()));

    }

}

優先度の設定はマッピングファイルでも設定可能である。

{
    // ↓ 追加
    "priority": 1,
    // ↑ 追加
    "request": {
        "method": "GET",
        "url": "/some/thing"
    },
    "response": {
        "status": 200,
        "body": "Hello world!",
        "headers": {
            "Content-Type": "text/plain"
        }
    }
}

レスポンスボディの内容を外部ファイル化する

レスポンスボディの内容をテストコードやマッピングファイル内に直接記述したくない場合、別のファイルに記述してそれを読み込むようにすることができる。
レスポンスボディの内容を記述したファイルはsrc/test/resources/__files配下に置く。
以下に設定例を示す。
以下のようなレスポンスボディを定義したファイルを準備する。

{
    "message": "Hello World"
}

テストコードにスタブを定義する場合は以下のように記述する。
.withBodyFile()の引数にはsrc/test/resources/__files以降のパスを記述する。

class SampleTest {

    @RegisterExtension
    static WireMockExtension mock = WireMockExtension.newInstance()
            .options(WireMockConfiguration.wireMockConfig().httpDisabled(false).port(3000))
            .build();

    @Test
    void test() {
        mock.stubFor(WireMock.get(WireMock.urlEqualTo("/some/thing"))
                .willReturn(WireMock.aResponse()
                        .withHeader("Content-Type", "text/plain")
                        // ↓ レスポンスボディを定義したファイルのパスを指定する
                         .withBodyFile("clientTest/get001.json")));
    }

}

マッピングファイルに定義する場合は以下のように記述する。
src/test/resources/__files以降のパスを指定する。

{
    "request": {
        "method": "GET",
        "url": "/some/thing"
    },
    "response": {
        "status": 200,
        // ↓ レスポンスボディを定義したファイルのパスを指定する
        "bodyFileName": "clientTest/get001.json",
        "headers": {
            "Content-Type": "text/plain"
        }
    }
}

リクエストを検証する

verify()メソッドを使用することで、実際に送信されたリクエストについて検証が可能である。
メソッドの第一引数に数値を渡すことで、そのリクエストの実行回数を検証することもできる。
詳しくはここを参照。

class SampleTest {

    @RegisterExtension
    static WireMockExtension wm1 = WireMockExtension.newInstance()
            .options(WireMockConfiguration.wireMockConfig().httpDisabled(false).port(3000))
            .build();

    @Test
    void test() {
        // verify前に実際にリクエストを送る処理を記述する
        
        wm1.verify(2, WireMock.getRequestedFor(WireMock.urlEqualTo("/api/test/query/headers?name=Bob"))
                .withQueryParam("name", WireMock.equalTo("Bob"))
                .withHeader("Content-Type", WireMock.equalTo("application/json"))
                .withHeader("Authorization", WireMock.equalTo("Bearer apiKey")));
    }

}

リクエストするたびにレスポンスを変更する

テストコードでもマッピングファイルでもどちらでも設定可能。
3回目までのレスポンスを定義してあるスタブに4回以上リクエストを送信した場合、 4回目以降は3回目に定義したレスポンスが返される。

  1. リクエストするたびにレスポンスを変更させたいスタブにinScenario(String)を設定する。
    引数にはシナリオ名を指定する。
  2. スタブが実行されるのに必要なwhenScenarioStateIs(String)を設定する。
    最初のリクエストで実行したいスタブにはScenario.STARTEDを設定する。
    2番目以降に実行したいスタブには任意の値を設定する。
  3. そのスタブが実行された後に実行したいスタブを指定する。
    willSetStateTo(String)の引数に次に実行したいスタブのwhenScenarioStateIs(String)で設定した値を指定する。

以下にテストコードで設定した例を示す。

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
import com.github.tomakehurst.wiremock.junit5.WireMockExtension;

class SampleTest {

    @RegisterExtension
    static WireMockExtension mock = WireMockExtension.newInstance()
            .options(WireMockConfiguration.wireMockConfig().httpDisabled(false).port(3000))
            .build();

    @Test
    void test() {
        mock.stubFor(WireMock.get(WireMock.urlEqualTo("/api/test/retry"))
                .inScenario("retry")
                .whenScenarioStateIs(Scenario.STARTED)
                .willSetStateTo("second")
                .willReturn(WireMock.aResponse()
                        .withStatus(500)
                        .withHeader("Content-Type", "application/json")
                        .withBody("{\"message\": \"Get Request Failed\"}")));

        mock.stubFor(WireMock.get(WireMock.urlEqualTo("/api/test/retry"))
                .inScenario("retry")
                .whenScenarioStateIs("second")
                .willSetStateTo("third")
                .willReturn(WireMock.aResponse()
                        .withStatus(500)
                        .withHeader("Content-Type", "application/json")
                        .withBody("{\"message\": \"Get Request Failed\"}")));

        mock.stubFor(WireMock.get(WireMock.urlEqualTo("/api/test/retry"))
                .inScenario("retry")
                .whenScenarioStateIs("third")
                .willReturn(WireMock.aResponse()
                        .withStatus(200)
                        .withHeader("Content-Type", "application/json")
                        .withBody("{\"message\": \"Get Request Success\"}")));
    }

}

上の設定をマッピングファイルで定義した場合、以下のようになる。

  1. リクエストするたびにレスポンスを変更するのに使用するスタブにscenarioNameを設定する。
  2. スタブが実行されるのに必要なrequiredScenarioStateを設定する。
    最初のリクエストで実行したいスタブにはStartedを設定する。2番目以降に実行したいスタブには任意の値を設定する。
  3. そのスタブが実行された後に実行したいスタブを指定する。
    newScenarioStateに次に実行したいスタブのrequiredScenarioStateを指定する。
{
  "mappings": [
    {
        "scenarioName": "retry",
        "requiredScenarioState": "Started",
        "newScenarioState": "second",
        "request": {
            "method": "GET",
            "url": "/api/test/retry"
        },
        "response": {
            "status": 500,
            "body":  "{\"message\": \"Get Request Failed\"}",
            "headers": {
                "Content-Type": "application/json"
            }
        }
    },
    {
        "scenarioName": "retry",
        "requiredScenarioState": "second",
        "newScenarioState": "third",
        "request": {
            "method": "GET",
            "url": "/api/test/retry"
        },
        "response": {
            "status": 500,
            "body":  "{\"message\": \"Get Request Failed\"}",
            "headers": {
                "Content-Type": "application/json"
            }
        }
    },
    {
        "scenarioName": "retry",
        "requiredScenarioState": "third",
        "request": {
            "method": "GET",
            "url": "/api/test/retry"
        },
        "response": {
            "status": 200,
            "body":  "{\"message\": \"Get Request Success\"}",
            "headers": {
                "Content-Type": "application/json"
            }
        }
    }
  ]
}

Wiremock-standalone版を使用する

上記はWiremockをJunitで使用する方法を説明したが、Wiremockにはスタンドアロン版も存在している。

このページからjarファイルをダウンロードし、 jarファイルが存在するディレクトリで以下のコマンドを実行することで、モックサーバを起動することが出来る。

java -jar wiremock-jre8-standalone-2.33.2.jar

スタブの定義は、jarファイルと同じ階層にmappingsフォルダと__filesフォルダを作成し、そこにスタブの定義ファイルを格納する。
スタブの定義方法はJunit版と同じである。