WireMockをローカル環境に導入する

この記事は[ZOZOテクノロジーズ #4 Advent Calendar 2020](https://qiita.com/advent-calendar/2020/zozo_tech4) 7日目の記事です。
昨日は @ahiru___ さんの「[MetricKit 入門](https://qiita.com/ahiru___/items/14e443435eb1a59d615b)」でした。ぜひご覧ください！

# はじめに

普段はGoでAPIやバッチの開発をしています。
Goでは実装コードに対してテストコードを記述してコードの品質を保障します。ところが外部APIなどを利用する場合は、テストコードだけで動作保証は少し心配ですよね。動作を保障するためには実際にAPIにリクエストして動作確認したいところです。しかしセキュリティやアカウント発行などの理由で気軽にリクエストできないことも多々あると思います。
この記事はモックフレームワークとして[WireMock](http://wiremock.org/) を個人的に導入した件を紹介します。

# 環境
- macOS Catalina 10.15.7
- docker 20.10.0

# なぜ WireMock
調べると[素晴らしいモックフレームワーク](#その他のフレームワーク)はいくつもありましたが、WireMockに決めた理由を下にあげました。

1. リクエスト・レスポンスのスタブをJSONのみで設定できる
2. レスポンスを外部ファイル化できる
3. リクエストの検証がリッチにできそう
4. スタブ設定はAPI経由でできる
5. docker対応
6. 個人的にとても使いやすかった！

# モックをいれた構成イメージ
以下のようにローカル環境の構成にモックを入れた構成を目標にします。環境をあまり汚したくないので、docker-composeで設定します。
![概要イメージ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4181/217504c9-6687-607e-0f25-bd246c095469.png)

# 起動

[DockerHub](https://hub.docker.com/r/rodolpheche/wiremock/)で公開されているイメージを使います。

```terminal
$ docker pull rodolpheche/wiremock

# 起動する
$ docker run -it --rm -p 8080:8080 rodolpheche/wiremock
```

http://localhost:8080/__admin にアクセスすると何も設定されていない状態です。

# 設定方法
大まかな流れは下のように行います。

1. WireMock起動
2. スタブ（リクエストとレスポンス）を作成
3. 作ったスタブを`mappings`ディレクトリに配置する

スタブに関しては、[ドキュメントページ](http://wiremock.org/docs/stubbing/)を参照してください。
例：/api/helloにGETリクエストでアクセスし、成功するスタブです。

```json:request_user1.json
{
    "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エラーとなるスタブです。


```json:request_user10.json
{
    "request": {
        "method": "GET",
        "url": "/user/10"
    },
    "response": {
        "status": 404,
        "headers": {
            "Content-Type": "application/json"
        },
        "bodyFileName": "path/to/response_404.json"
    }
}
```

以下が外部ファイルとしてリクエストと別で設定できます。編集に苦労しそうな複雑なレスポンスや長いレスポンスなどが必要な場合は便利です。

```json:response_404.json
{
    "status": 404,
    "message": "record not found"
}
```

外部ファイルは`__files`ディレクトリに保存します。

```
└── wiremock
    └── home
        ├── __files ←このディレクトリに配置する
        └── mappings 
```

## リクエストの検証
WireMockには [Request Matching](http://wiremock.org/docs/request-matching/) という機能があり、リクエストの動作検証に役に立ちます。

* URL
* HTTPメソッド
* クエリパラメータ
* ヘッダ
* ベーシック認証
* クッキー
* リクエストボディ
* Multipart/form-data

この機能がJSONのみで設定できてしまうのはとても素晴らしい！テストシナリオなど組み立てるときに役に立ちそうです。

### 検証例

ヘッダのトークンを限定しているリクエスト

```javascript
// ヘッダの`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"
    }
}
```

リクエストボディ

```javascript

// 正規表現が使える
{
  "request": {
    "bodyPatterns": "/api/([a-z]*)\\?query=all"
    ...
  },
  ...
}

```
[ドキュメント](http://wiremock.org/docs/request-matching/)には多くの良いサンプルがあります。[そちら](http://wiremock.org/docs/request-matching/)を確認するのをおすすめします。

## API経由でスタブ設定する
実はスタブはWireMockが用意しているAPIから登録ができます。( [参照: Admin API](http://wiremock.org/docs/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](http://wiremock.org/docs/api/) は上に示した以外にも様々に用意されています。
[ドキュメント：WireMock admin API](http://wiremock.org/docs/api/)

### 登録
Terminalなどでコマンド経由して登録もできますが、Postmanなどで登録するのが簡単だと思います。
作ったスタブは`http://localhost:8080/__admin/mappings`経由でモックサーバに登録できます。
![Postmanでの登録画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4181/33f39d41-a139-fffa-13d2-dc5442c4bdbe.png)

### 登録の自動化
APIで設定ができるということは、スクリプトなどを用意すれば登録など自動化できそうです。
![load-stubs.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4181/cf10f789-dd51-b4bf-fc87-cb393c984e98.png)

#### スクリプト化する
スクリプトで設定ファイルの読み込み、各ファイルに対して登録APIを実行します。
今回はシェルスクリプトで以下のように実装しました。

```bash:setup_mock.sh
# 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が発行されます。

```terminal
$ 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を実行する

```bash
# 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管理しているのがわかります。

```json:user1-849c36d2-c592-42f4-8c01-c4f81b8ec849.json
{
  "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を組み込んで開発しています。

```yml:docker-compose.yml
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](https://mockapi.io/)   |Webベース。簡単にモックのエンドポイントが作れる。                        |
|[mockify](https://github.com/brianmoran/mockify)  |Go製。リクエストとレスポンスを簡単に設定できるライブラリ. RESTに対応。|
|[Mocky](https://github.com/julien-lafont/Mocky)   |Scalaベース。APIリクエストとレスポンスをモッキングできるツール|
|[Karate](https://github.com/intuit/karate)       |TestフレームワークでAPIモックもできる。RESTだけではなくGraphQL、gRPCなども対応している|


# まとめ

この記事ではモックフレームワークのWireMockの機能の一部と自動化などを紹介しましたが、紹介しきれなかった機能もまだまだあります。WireMockと[Newman](https://github.com/postmanlabs/newman)などと組み合わせてテストシナリオの自動化なども実現できそうです。API開発やモックサーバを検討している方々の参考になれば幸いです。テストの世界は奥が深い。

# 参考サイト

- [WireMock](http://wiremock.org/)
- [wiremock/wiremock](https://hub.docker.com/r/wiremock/wiremock)