WireMockを使って通信に関するテストをやってみよう

java_logo2

みなさん、こんにちは。週に2回ぐらいラーメンが食べたくなる齋藤です。 現在、うちのチームでは、テストにWireMockを使ってみようということで HTTP通信を行う部分のコードを書いている自分が試しています。

こちらの記事も合わせてご覧ください。

今回の記事では以下の内容について書いてみたいと思います。

  • 静的なポート番号の指定によるモックサーバの起動とポート番号の取得
  • 動的なポート番号設定
  • マッピングの設定方法とリクエストのマッチング
  • マッピングパスの変更
  • 動的なレスポンス
  • レスポンステンプレートのヘルパーの追加と利用

静的なポート番号の指定によるモックサーバとポート番号の取得

WireMockからjUnitのルールが提供されており、以下のような形で利用することができます。 これは一番最初に書かれた記事で紹介されています。 内部でnettyが自動的に立ち上がり、モックアップが可能です。

@Rule
public WireMockRule wireMockRule = new WireMockRule(8080);

ポート番号の取得をしてみます。 アサーションはassertJを使っています。

@Test
public void getPort(){
  assertThat(wireMockRule.port()).isEqualTo(8080);
}

httpsのポート番号も指定してみて取得して見ましょう。 さっきのコードに加えて第二引数が増えています。

@Rule
public WireMockRule wireMockRule = new WireMockRule(8080, 443);
@Test
public void getHttpsPort(){
  assertThat(wireMockRule.httpsPort()).isEqualTo(443);
}

今回は静的にポート番号を指定したので ポート番号が取得できても、あまり嬉しくありません。 次はポートを動的に設定してみましょう。

今度は動的にポート設定してみる

static importを一文追加して、chainする形で記述可能です。

import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
...

@Rule 
public WireMockRule wireMockRule = new WireMockRule(wireMockConfig()
            .dynamicPort()
            .dynamicHttpsPort());

こちらも先ほど静的にポートを指定した場合と同様に ポート番号の取得が可能です。

マッピングの指定方法とリクエストのマッチング

マッピングのファイルを固有の場所に配置することで WireMockは自動的に認識して処理を行ってくれます。 デフォルトでは、起動パスからsrc/test/resources/mappingsの下を探索します。

利用したサンプルです。 今回はユーザを登録するようなAPIのモックを想定しています。

{
    "request": {
        "method": "POST",
        "url": "/users",
        "headers": {
            "Content-Type": {
                "matches": "application/json"
            }
        }
    },
    "response": {
        "status": 200,
        "body": "{\"name\": \"John\"}",
        "headers": {
            "Content-Type": "application/json"
        }
    }
}

今回のサンプルではrequestのContent-Typeに対してmatchesの値を与えています。 これは特定のRequestに対してマッチさせる機能で 今回のサンプルでは、Content-Typeがapplication/jsonの場合にこのモックレスポンスが返却されます。

他にも色々リクエストのマッチングのために機能が用意されています。 公式サイトには以下のマッチング方法があると記述されています。

  • URL
  • HTTP Method
  • Query parameters
  • Headers
  • Basic認証
  • Cookies
  • Request body

詳しくは公式サイトのRequest Matchingをご覧ください。

上記サンプルでは、bodyという形でエスケープされているjsonを記述しています。これは辛い。

というわけで上記サンプルはjsonBodyというキーでjson形式で 書き直すことができます。

{
    "request": {
        "method": "POST",
        "url": "/users",
        "headers": {
            "Content-Type": {
                "matches": "application/json"
            }
        }
    },
    "response": {
        "status": 200,
        "jsonBody": {
            "name": "John"
        },
        "headers": {
            "Content-Type": "application/json"
        }
    }
}

また、レスポンスボディにファイルを指定できる機能も存在します。 bodyFileNameに指定したファイルををレスポンスボディに指定するようなサンプルです。 デフォルトではsrc/test/resources/__files/path/to/hoge.htmlに配置されているファイルを参照するようです。

{
    "request": {
        "method": "GET",
        "url": "/hoge.html"
    },
    "response": {
        "status": 200,
        "bodyFileName": "path/to/hoge.html"
    }
}

ちなみに、__filesに配置されたファイルはmappingsに配置されたマッピングファイルのいずれにもマッチしなかった場合は URLと一致しているファイルが返却されるようです。

マッピングパスの変更

テストによって帰ってくるレスポンスの変更がしたくなると思います。 ここではマッピングファイルのパスの変更をしてみます。 以下の例では [src/test/resources/hoge/fuga/mappings]フォルダの下を見に行ってくれます。

@Rule 
public WireMockRule wireMockRule = new WireMockRule(wireMockConfig()
            .dynamicPort()
            .dynamicHttpsPort()
            .usingFilesUnderDirectory("src/test/resources/hoge/fuga"));

他にもclasspathからファイルを読むusingFilesUnderClasspathというメソッドが WireMockConfigというクラスに定義されていますので以下のような形で記述することが可能です。

@Rule 
public WireMockRule wireMockRule = new WireMockRule(wireMockConfig()
            .dynamicPort()
            .dynamicHttpsPort()
            .usingFilesUnderClasspath("path/to/hoge/fuga"));

動的なレスポンス

ここまで、リクエストのマッチングやマッピングファイルの配置場所について説明してきましたが 動的なレスポンスを返却するような機能も用意されています。

WireMockの設定は以下のようになります。

@Rule
public WireMockRule wireMockRule = new WireMockRule(wireMockConfig()
    .extensions(new ResponseTemplateTransformer(false));

また、モックの設定は以下のように記述します。templateの記法はhandlebarsのようです。 以下の例ではリクエストのパスがレスポンスのボディに書かれます。

{
    "request": {
        "urlPath": "/path-template"
    },
    "response": {
        "body": "{{request.path}}",
        "transformers": ["response-template"]
    }
}

他にもいくつか記法があるようです。詳しくは公式サイトのこちらのページをご覧ください。

レスポンステンプレートのヘルパーの追加

今度はレスポンスのテンプレートエンジンにヘルパーを追加してみます。

以下の例がマッピングファイルがヘルパーに処理されます。 1つ目のパラメータとして指定されたjsonを2つ目のパラメータに指定されたパスに従って 文字列を返却するようなヘルパーです。

{
    "request": {
        "method": "POST",
        "url": "/users",
        "headers": {
            "Content-Type": {
                "matches": "application/json"
            }
        }
    },
    "response": {
        "status": 200,
        "transformers": [
            "response-template"
        ],
        "jsonBody": {
            "username": "{{path request.body 'username'}}",
        },
        "headers": {
            "Content-Type": "application/json"
        }
    }
}

pathという名前でヘルパーを追加しました。 もう少しエラー処理を追加した方が良いと思われますが、サンプルということで。 今回はJSONの処理用のライブラリとしてJsonPathを使っています。

@Rule
public WireMockRule wireMockRule = new WireMockRule(wireMockConfig()
            .dynamicPort()
            .dynamicHttpsPort()
            .extensions(new ResponseTemplateTransformer(false, "path", (body, option) -> {
                return JsonPath.parse(body.toString()).read(option.param(0).toString(), String.class);
            })));

終わりに

実際に気になった部分に関してWireMockで記述してみました。 いかがだったでしょうか?

公式のドキュメントには色々な機能が書かれており、様々なサンプルが記載されています。 今回の記事では紹介しなかった、Proxyの機能やレスポンスの遅延の機能なども 存在しています。 WireMockは非常に強力なモックサーバだと思います。試してみてはいかがでしょうか?

ラーメン食べたい。