この記事はZOZOテクノロジーズ #4 Advent Calendar 2020 7日目の記事です。
昨日は @ahiru___ さんの「MetricKit 入門」でした。ぜひご覧ください!
はじめに
普段はGoでAPIやバッチの開発をしています。
Goでは実装コードに対してテストコードを記述してコードの品質を保障します。ところが外部APIなどを利用する場合は、テストコードだけで動作保証は少し心配ですよね。動作を保障するためには実際にAPIにリクエストして動作確認したいところです。しかしセキュリティやアカウント発行などの理由で気軽にリクエストできないことも多々あると思います。
この記事はモックフレームワークとしてWireMock を個人的に導入した件を紹介します。
環境
- macOS Catalina 10.15.7
- docker 20.10.0
なぜ WireMock
調べると素晴らしいモックフレームワークはいくつもありましたが、WireMockに決めた理由を下にあげました。
- リクエスト・レスポンスのスタブをJSONのみで設定できる
- レスポンスを外部ファイル化できる
- リクエストの検証がリッチにできそう
- スタブ設定はAPI経由でできる
- docker対応
- 個人的にとても使いやすかった!
モックをいれた構成イメージ
以下のようにローカル環境の構成にモックを入れた構成を目標にします。環境をあまり汚したくないので、docker-composeで設定します。
起動
DockerHubで公開されているイメージを使います。
$ docker pull rodolpheche/wiremock
# 起動する
$ docker run -it --rm -p 8080:8080 rodolpheche/wiremock
http://localhost:8080/__admin にアクセスすると何も設定されていない状態です。
設定方法
大まかな流れは下のように行います。
- WireMock起動
- スタブ(リクエストとレスポンス)を作成
- 作ったスタブを
mappings
ディレクトリに配置する
スタブに関しては、ドキュメントページを参照してください。
例:/api/helloにGETリクエストでアクセスし、成功するスタブです。
{
"request": {
"method": "GET",
"url": "/user/1"
},
"response": {
"status": 200,
"jsonBody": {
"status": "Success",
"name": "xxxx",
"email": "xxx@example.com"
},
"headers": {
"Content-Type": "application/json"
}
}
}
WireMockを起動しつつ /home/wiremock
以下の mappings
ディレクトリにスタブ設定したJSONファイルを配置します。
└── wiremock
└── home
├── __files
└── mappings ←ここにいれる
レスポンスの外部ファイル化
レスポンスを外部ファイルとして保存できます。要するに永続化できるのでファイルをレポジトリなどで管理できます。
例:/api/helloにGETリクエストでアクセスし、404エラーとなるスタブです。
{
"request": {
"method": "GET",
"url": "/user/10"
},
"response": {
"status": 404,
"headers": {
"Content-Type": "application/json"
},
"bodyFileName": "path/to/response_404.json"
}
}
以下が外部ファイルとしてリクエストと別で設定できます。編集に苦労しそうな複雑なレスポンスや長いレスポンスなどが必要な場合は便利です。
{
"status": 404,
"message": "record not found"
}
外部ファイルは__files
ディレクトリに保存します。
└── wiremock
└── home
├── __files ←このディレクトリに配置する
└── mappings
リクエストの検証
WireMockには Request Matching という機能があり、リクエストの動作検証に役に立ちます。
- URL
- HTTPメソッド
- クエリパラメータ
- ヘッダ
- ベーシック認証
- クッキー
- リクエストボディ
- Multipart/form-data
この機能がJSONのみで設定できてしまうのはとても素晴らしい!テストシナリオなど組み立てるときに役に立ちそうです。
検証例
ヘッダのトークンを限定しているリクエスト
// ヘッダの`authorization`の値が「token xxxxxxxxxxxxx」のリクエストを期待している設定
{
"request": {
"method": "GET",
"headers": {
"authorization": {
"equalTo": "token xxxxxxxxxxxxx",
"caseInsensitive": true
}
},
"url": "/api/users?from=2020-12-25"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"bodyFileName": "2020-12-25.json"
}
}
リクエストボディ
// 正規表現が使える
{
"request": {
"bodyPatterns": "/api/([a-z]*)\\?query=all"
...
},
...
}
ドキュメントには多くの良いサンプルがあります。そちらを確認するのをおすすめします。
API経由でスタブ設定する
実はスタブはWireMockが用意しているAPIから登録ができます。( 参照: Admin API )
操作 | URL | メソッド |
---|---|---|
一覧 | http://[WireMockのホスト]:[ポート]/__admin/mappings | GET |
登録 | http://[WireMockのホスト]:[ポート]/__admin/mappings | POST |
保存 | http://[WireMockのホスト]:[ポート]/__admin/mappings/save | POST |
削除 | http://[WireMockのホスト]:[ポート]/__admin/mappings | DELETE |
WireMockの API は上に示した以外にも様々に用意されています。
ドキュメント:WireMock admin API
登録
Terminalなどでコマンド経由して登録もできますが、Postmanなどで登録するのが簡単だと思います。
作ったスタブはhttp://localhost:8080/__admin/mappings
経由でモックサーバに登録できます。
登録の自動化
APIで設定ができるということは、スクリプトなどを用意すれば登録など自動化できそうです。
スクリプト化する
スクリプトで設定ファイルの読み込み、各ファイルに対して登録APIを実行します。
今回はシェルスクリプトで以下のように実装しました。
# WireMockサーバがlocalhost:8080で動作している場合
WIREMOCK_URL="http://localhost:8080/__admin/mappings"
# スタブJSONのフォルダをスキャンして、全JSONファイルに対して登録APIを実行する
for jsonfile in mocks/*.json ;
do
payload=$(jq 'walk(if type == "object" then with_entries(select(.key[0:1] != "#")) else . end)' $jsonfile)
curl -X POST -d"$payload" $WIREMOCK_URL
done;
登録の実行例
実行すると下のようなレスポンスを受け取ります。スタブが正常に登録されるとUUIDが発行されます。
$ wiremock ./setup_mock.sh #スクリプト実行
{ "request": { "method": "GET", "url": "/user/1" }, "response": { "status": 200, "jsonBody": { "status": "Success", "name": "xxxx", "email": "xxx@example.com" }, "headers": { "Content-Type": "application/json" } } }
{
"id" : "7c9a95b2-132d-49be-8ae8-06dad2cae500",
"request" : {
"url" : "/user/1",
"method" : "GET"
},
"response" : {
"status" : 200,
"jsonBody" : {
"status" : "Success",
"name" : "xxxx",
"email" : "xxx@example.com"
},
"headers" : {
"Content-Type" : "application/json"
}
},
"uuid" : "7c9a95b2-132d-49be-8ae8-06dad2cae500"
}%
スタブの保存する
登録したスタブを保存するには上のスクリプト実行後に保存APIを実行する
# WireMockサーバがlocalhost:8080で動作している場合
WIREMOCK_SAVE_URL="http://localhost:8080/__admin/mappings/save"
# 登録しているスタブが永続化されます
curl -X POST $WIREMOCK_SAVE_URL
スクリプト実行するとmappings
に Path+[発行されたUUID].json
というファイルで保存されます。
└── wiremock
└── home
├── __files
└── mappings ←このディレクトリに保存される
└── user1-849c36d2-c592-42f4-8c01-c4f81b8ec849.json
JSONファイルの内容は、スタブ設定しているものと同じです。そしてWireMockはファイルをID管理しているのがわかります。
{
"id" : "849c36d2-c592-42f4-8c01-c4f81b8ec849",
"request" : {
"url" : "/user/1",
"method" : "GET"
},
"response" : {
"status" : 200,
"jsonBody" : {
"status" : "Success",
"name" : "xxxx",
"email" : "xxx@example.com"
},
"headers" : {
"Content-Type" : "application/json"
}
},
"uuid" : "849c36d2-c592-42f4-8c01-c4f81b8ec849",
"persistent" : true,
"insertionIndex" : 0
}
docker-compose設定
実際の環境では下のように開発するアプリとmockを組み込んで開発しています。
version: "3"
services:
app:
build:
context: .
dockerfile: "docker/Dockerfile"
volumes:
- .:/github.com/xxxxx/mkn-wiremock
ports:
- 8888:8888
tty: true
networks:
- mynetwork
mock:
image: wiremock/wiremock:latest-alpine
volumes:
- ".data/wiremock/stubs:/home/wiremock"
ports:
- 8080:8080
networks:
- mynetwork
restart: on-failure
networks:
mynetwork:
ポイント
WireMockはスタブを配置するディレクトリを/home/wiremock
を期待しているので設定をします。
上の場合、ローカル環境の.data/wiremock/stubs
ディレクトリをそれにあてています。
その他のフレームワーク
調べたモックフレームワークです。
候補 | 概要 |
---|---|
Mockapi.io | Webベース。簡単にモックのエンドポイントが作れる。 |
mockify | Go製。リクエストとレスポンスを簡単に設定できるライブラリ. RESTに対応。 |
Mocky | Scalaベース。APIリクエストとレスポンスをモッキングできるツール |
Karate | TestフレームワークでAPIモックもできる。RESTだけではなくGraphQL、gRPCなども対応している |
まとめ
この記事ではモックフレームワークのWireMockの機能の一部と自動化などを紹介しましたが、紹介しきれなかった機能もまだまだあります。WireMockとNewmanなどと組み合わせてテストシナリオの自動化なども実現できそうです。API開発やモックサーバを検討している方々の参考になれば幸いです。テストの世界は奥が深い。