nikkie-ftnextの日記

イベントレポートや読書メモを発信

WireMockは簡単に使えてすごい!JSONファイルで設定してDockerで動かす例 〜ちさたきを添えて🐟〜

はじめに

もしもしもしもし〜1、nikkieです。

HTTPサーバをモックできるWireMockを最近触りました。
この技術、「非常に簡単に間違えずに使える!」と感動したので、今回アウトプットします。

目次

WireMockとは

HTTPサーバをモックできます2
2つの用途で使えるそうです3

  • stubbing(スタブ)
    • 特定のリクエストに対して、前もって指定されたレスポンスを返す
  • verification(検証)
    • 後でチェックできるよう、入ってくるリクエストをキャプチャする

REST APIJavaAPIも提供されていますが、今回はJSONファイルからスタブの設定4を触りました。
これが非常に簡単だったんです!

WireMockをJSONファイルで設定してDockerで動かす

Configuring and running WireMock in Docker | WireMock

ドキュメントの「Mounting stub mapping files」を参照します。

  • 作業ディレクトリに、JSONを配置するための2つのディレクトを用意する
    • mappings
    • __files
  • mappings__filesがあるディレクトリをDockerコンテナの/home/wiremockにマウントする
    • 👉-v $PWD:/home/wiremock

新しい概念がたくさん出てきましたが、1つ1つ見ていきましょう。
モックするサーバは『リコリス・リコイル』の「さかなー」「ちんあなごー」を模します5

GET /chinanago で「さかなー」と返すように設定する

mappingsJSONファイルtakina.jsonを作ります。

{
    "request": {
        "urlPath": "/chinanago",
        "method": "GET"
    },
    "response": {
        "status": 200,
        "body": "さかなー🐟"
    }
}

パス/chinanago6GETメソッドでリクエストされたときのスタブを設定しています(requestの部分)。
レスポンスは200 OKを返し、ボディは「さかなー🐟」と設定しました(responseの部分)。

設定値について詳しくは Matching and filtering HTTP requests in WireMock | WireMock (1) に記載されています。

mappings/takina.jsonを置いた状態でWireMockのDockerイメージをrunしましょう!7

docker run -it --rm \
  -p 8080:8080 \
  --name chisataki-mock \
  -v $PWD:/home/wiremock \
  wiremock/wiremock:2.35.0

別のシェル8でリクエストを送ってみましょう。

$ curl http://localhost:8080/chinanago
さかなー🐟

さかなー🐟🐟🐟🐟!!!!

POST /takina にchinanagoのJSONが送られたら「さかなー」と返すように設定する

次はPOSTリクエスをモックしてみましょう。
mappings/takina.jsonrequestを変えていきます。

⚒変える箇所

  • urlPath(完全に好みで/takinacurlしたかったので変えました)
  • method:POSTリクエストを指定
  • headersを追加
    • Content-Typeがapplication/jsonの場合のスタブです9
  • bodyPatternsを追加
    • {"lines": "chinanago"}というJSONに一致するボディのリクエストに対するスタブです
{
    "request": {
-        "urlPath": "/chinanago",
-        "method": "GET"
+        "urlPath": "/takina",
+        "method": "POST",
+        "headers": {
+            "Content-Type": {
+                "equalTo": "application/json"
+            }
+        },
+        "bodyPatterns": [
+            {
+                "equalToJson": {
+                    "lines": "chinanago"
+                }
+            }
+        ]
    },
    "response": {
        "status": 200,
        "body": "さかなー🐟"
    }
}

設定を変えたのでWireMockのイメージは一度止めて、再度docker run(コマンドはこれまでと同じです)。
別のシェルでcurlします。

$ curl http://localhost:8080/takina -H 'Content-Type: application/json' -d '{"lines": "chinanago"}'
さかなー🐟

さかなー🐟🐟🐟🐟🐟🐟🐟🐟!!!!!!!!

.request.bodyPatternsequalToJsonJSONで指定しましたが、ドキュメント(1)によると文字列リテラルとしても指定できます(「JSON with string literal」でページ内を検索すると見つかります)

        "bodyPatterns": [
            {
                "equalToJson": "{\"lines\": \"chinanago\"}"
            }
        ]

POST /takina の「さかなー」のレスポンスをJSONファイルを使って設定する

Mock API Response Templating | WireMock (2)

mappings/takina.json.response.bodyJSON形式の文字列リテラルも指定できます。

{
    "request": {
        "urlPath": "/takina",
        "method": "POST",
        "headers": {
            "Content-Type": {
                "equalTo": "application/json"
            }
        },
        "bodyPatterns": [
            {
                "equalToJson": "{\"lines\": \"chinanago\"}"
            }
        ]
    },
    "response": {
        "status": 200,
-        "body": "さかなー🐟"
+        "body": "{\"lines\": \"さかなー🐟\"}"
    }
}
$ curl -s http://localhost:8080/takina -H 'Content-Type: application/json' -d '{"lines": "chinanago"}' | jq .
{
  "lines": "さかなー🐟"
}

JSONの、さかなー🐟🐟🐟🐟!!!!

ドキュメント(2)によると、responsebodyは代わりにbodyFileNameを使って__files下に配置したJSONファイルの内容をレスポンスとして返せます(「Templated body file」)10

__files/takina.jsonを置きましょう。

{
    "lines": "さかなー🐟"
}

mappings/takina.jsonからこのファイルを使うように変更します。
bodyFileNameには__files以下の相対パスを書くようです。

{
    "request": {
        "urlPath": "/takina",
        "method": "POST",
        "headers": {
            "Content-Type": {
                "equalTo": "application/json"
            }
        },
        "bodyPatterns": [
            {
                "equalToJson": "{\"lines\": \"chinanago\"}"
            }
        ]
    },
    "response": {
        "status": 200,
-        "body": "{\"lines\": \"さかなー🐟\"}"
+        "bodyFileName": "takina.json"
    }
}
$ curl -s http://localhost:8080/takina -H 'Content-Type: application/json' -d '{"lines": "chinanago"}' | jq .
{
  "lines": "さかなー🐟"
}

__filesを使って設定したJSONの、さかなー🐟🐟🐟🐟🐟🐟🐟🐟!!!!!!!!

WireMockのDockerイメージの挙動は以下のようです。
-vでマウントしているので、ホスト側のファイルを編集すると、コンテナ内のファイルの内容も変わります

  • mappings側は起動時にメモリに読み込むようでdocker run中にファイルの内容が変わっても再度docker runするまで反映されない
  • __filesはリクエストが来るたびに参照される(メモリに読み込まれていない)ようで、docker runしたままでも返されるレスポンスが変わる

コンテナを止めずに__files/takina.jsonを変えてみます。

{
-    "lines": "さかなー🐟"
+    "lines": "さかなー🐟🐟🐟🐟"
}
$ curl -s http://localhost:8080/takina -H 'Content-Type: application/json' -d '{"lines": "chinanago"}' | jq .
{
  "lines": "さかなー🐟🐟🐟🐟"
}

まとめ:WireMockをJSONファイルで設定する

WireMockを設定するJSONファイルの配置

  • mappingsディレクト
    • スタブするリクエストを設定するJSONファイルを置く
      • レスポンスも設定可能(__filesに切り出すこともできる)
    • requestでスタブするリクエストを設定
  • __filesディレクト
    • レスポンスとするJSONファイルを置ける
    • .response.bodyFileName相対パスで指定できる

docker runでは「mappings__filesがあるディレクトリ」を/home/wiremockにマウントする。

この記事ではJSONファイルは1つずつしか置いていませんが、複数置くこともできます。

終わりに

JSONファイルでのWireMockの設定が簡単!」という感動からこの記事にアウトプットしました。
思うに「正しい使い方を簡単に、誤った使い方を困難に」の好例だと思います。
ルールに沿ってJSONファイルを置いてdocker runするだけでモックサーバが立ち上がる、簡単!
JSONの書き方を間違えたときはdocker runで落ちてパースに失敗したエラーメッセージが表示されます。
jqコマンド素振り11など、読み解くために必要な知識があったというバイアスがかかっているかもしれませんが、間違えても正しい道に戻りやすかったです。

また、WireMockには「80/20ルール」12も該当するように思われます。
Gitなどと同様、WireMockも20%を知ることで、日常的なニーズの80%はまかなえそうです。

今回はJSONファイルを使いましたが、REST APIからの設定に使える(?)Pythonライブラリもあるみたいでした。
素振り材料ですね。

GitHub - wiremock/python-wiremock: Python WireMock Admin API Client

今回のサンプルコードはこちらです(たきなをGET、千束をPOSTで設定しました)


  1. リコリス・リコイル6話で千束が言ってました。
  2. ドキュメントより「WireMock is an HTTP mock server.
  3. ドキュメントより「At its core it is web server that can be primed to serve canned responses to particular requests (stubbing) and that captures incoming requests so that they can be checked later (verification).
  4. ドキュメントより「Additionally, stubs can be configured via JSON files.
  5. 例に使った理由は、好みです!
  6. takina.json.request.urlPathですが、ドキュメント(1)の「URL matching」を参照すると他にもやり方があります。正規表現も使えるそうです
  7. この時点では__filesディレクトリは使っていませんが、__filesディレクトリがない場合はdocker run -vのマウントにより__filesディレクトリができました(権限はrootではなく、ホスト側の現在のユーザでした)
  8. docker run-dを指定していないので、WireMockの出力が表示された状態です。-dを指定して同じシェルでcurlしてもいいと思います!
  9. Content-TypeのようなHTTPヘッダー名は「大文字小文字を区別しない」んですよね(contenT-typEなどのように変えてお試しください)。ヘッダーの値については、大文字小文字を区別しないでマッチするように指定する方法("caseInsensitive": true)がドキュメント(1)の「Case-insensitive equality」に載っています
  10. この記事の設定例だとあまり嬉しさを感じないかもしれませんが、「Templated body file」には"bodyFileName": "files/{{request.pathSegments.[1]}}"リクエストに応じたファイルを返す設定例があります
  11. JSON Linesをjqコマンドで扱う方法、『jqハンドブック』で完全に理解した! - nikkie-ftnextの日記
  12. Clean Agile』にあります。過去に言及したエントリはこちら:はてなスター、好きです!〜引用スターを知ってますます好きに〜 - nikkie-ftnextの日記