WireMockをスタンドアローンで使うときの基本的なメモ

# 概要

WireMockをスタンドアローンで使うときの基本的なメモです。大概のことは公式のドキュメントで説明されています。

**環境**

* Windows 10 Professional
* WireMock 2.20
* curl

**参考**

* WireMock
    * [WireMock User Documentation] (http://wiremock.org/docs/)

# 準備

WireMockの[Getting Started](http://wiremock.org/docs/getting-started/)ページからStandaloneで実行できるjarファイルをダウンロードします。
この記事では`wiremock-standalone-2.20.0.jar`を利用しました。
プロジェクトルートとなるディレクトリを作成してその直下にjarを配置し、起動用と停止用のbatファイルを作成しました。

```text
Project-Root/
  |
  +--- wiremock-standalone-2.20.0.jar
  |
  +--- startup.bat
  +--- shutdown.bat
```

## Startup

下記の内容でstartup.batファイルを作成します。起動するときはこのbatファイルを実行します。

```bat
java -jar .\wiremock-standalone-2.20.0.jar
```

起動オプションを確認するには`--help`を付けて実行します。よく使いそうなオプションとしては次のものがありました。

|option           |description    |
|:----------------|:--------------|
|\--bind-address  |The IP to listen connections |
|\--port          |The port number for the server to listen on (default: 8080). 0 for dynamic port selection. |
|\--root-dir      |Sets the root directory, under which mappings and __files reside. This defaults to the current directory. |
|\--verbose       |Enable verbose logging to stdout |

## Shutdown

下記の内容でshutdown.batファイルを作成します。停止するときはこのbatファイルを実行します。
停止するには下記のエンドポイントへpostするようです。この例ではcurlを利用しています。

```bat
curl -X POST --header "Content-Type: application/json" --header "Accept: application/json" "http://localhost:8080/__admin/shutdown"
```

# Stubbing

リクエストに対してどのようなレスポンスを返すかをjson形式のファイルに記述します。デフォルトのファイルの作成場所は、jarファイルと同じディレクトリの`mappings`というディレクトリになります。
jsonファイルの名前は任意です。

```text
Project-Root/
  |
  +--- wiremock-standalone-2.20.0.jar
  |
  +--- startup.bat
  +--- shutdown.bat
  |
  +--- mappings/
```

## 簡単なスタブの例

下記の内容で`200_hello_world.json`というファイルをmappingsディレクトリに作成します。

```text
Project-Root/
  |
  +--- mappings/
        |
        +--- 200_hello_world.json
```

このスタブは`/greeting`へのGETリクエストに対して、テキストで"Hello World!"というレスポンスを行います。

```json:200_hello_world.json
{
  "request": {
    "method": "GET",
    "url": "/greeting"
  },
  "response": {
    "status": 200,
    "body": "Hello world!",
    "headers": {
      "Content-Type": "text/plain"
    }
  }
}
```

curlでアクセスしてみます。

```text
> curl -v http://localhost:8080/greeting
*   Trying ::1...
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /greeting HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.46.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: text/plain
< Matched-Stub-Id: a2c6ba06-2187-4132-b11d-663e0a58f0e0
< Transfer-Encoding: chunked
< Server: Jetty(9.2.z-SNAPSHOT)
<
Hello world!* Connection #0 to host localhost left intact
```

## レスポンスをJSON形式にする

jsonBodyにjsonを書き、Content-Typeに"application/json"を指定します。

```json:200_hello_world.json
{
  "request": {
    "method": "GET",
    "url": "/greeting"
  },
  "response": {
    "status": 200,
    "jsonBody": {
      "message": "Hello world!"
    },
    "headers": {
      "Content-Type": "application/json"
    }
  }
}
```

curlでアクセスしてみます。

```text
> curl -v http://localhost:8080/greeting
*   Trying ::1...
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /greeting HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.46.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: application/json
< Matched-Stub-Id: 6548a90d-f190-4f81-b425-abe696af5a3b
< Transfer-Encoding: chunked
< Server: Jetty(9.2.z-SNAPSHOT)
<
{"message":"Hello world!"}* Connection #0 to host localhost left intact
```

## レスポンスするJSONを別ファイルで管理する

レスポンスするJSONをマッピングファイルには書かず、別のファイルで管理することもできます。デフォルトのファイルの作成場所は、jarファイルと同じディレクトリの`__files`というディレクトリになります。
この例ではレスポンスしたいJSONを`__fils/hello_world.json`で作成しました。

```text
Project-Root/
  |
  +--- __files
  |     |
  |     +--- hello_world.json
  |
  +--- mappings/
        |
        +--- 200_hello_world_all.json
```

bodyFileNameに読み込むjsonファイルを指定します。

```json:200_hello_world_all.json
{
  "request": {
    "method": "GET",
    "url": "/greeting/world"
  },
  "response": {
    "status": 200,
    "bodyFileName": "hello_world.json",
    "headers": {
      "Content-Type": "application/json"
    }
  }
}
```

レスポンスしたいJSONを下記のように定義しました。

```json:hello_world.json
[
  {
    "lang": "ja",
    "greeting": "こんにちは世界！"
  },
  {
    "lang": "yi",
    "greeting": "העלא וועלט!"
  },
  {
    "lang": "zu",
    "greeting": "Sawubona Mhlaba!"
  },
  {
  "lang": "uz",
  "greeting": "Salom Dunyo!"
  },
  {
  "lang": "ur",
  "greeting": "ہیلو دنیا!"
  },
  {
  "lang": "ta",
  "greeting": "ஹலோ உலகம்!"
  },
  {
  "lang": "si",
  "greeting": "හෙලෝ වර්ල්ඩ්!"
  }
]
```

curlでアクセスしてみます。

```text
> curl -v http://localhost:8080/greeting/world
*   Trying ::1...
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /greeting/world HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.46.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: application/json
< Matched-Stub-Id: 584c6236-7b2e-400f-89a2-98cabd551455
< Transfer-Encoding: chunked
< Server: Jetty(9.2.z-SNAPSHOT)
<
[
  {
    "lang": "ja",
    "greeting": "こんにちは世界！"
  },
  {
    "lang": "yi",
    "greeting": "העלא וועלט!"
  },
  {
    "lang": "zu",
    "greeting": "Sawubona Mhlaba!"
  },
  {
  "lang": "uz",
  "greeting": "Salom Dunyo!"
  },
  {
  "lang": "ur",
  "greeting": "ہیلو دنیا!"
  },
  {
  "lang": "ta",
  "greeting": "ஹலோ உலகம்!"
  },
  {
  "lang": "si",
  "greeting": "හෙලෝ වර්ල්ඩ්!"
  }
]
* Connection #0 to host localhost left intact
```

## マッピングファイルのディレクトリを指定する

マッピングファイルのディレクトリを変えたい場合は、WireMock起動時に`--root-dir`オプションで指定します。
この例では`system_a`ディレクトリ下にあるマッピングファイルを利用するように指定しています。

```text
java -jar .\wiremock-standalone-2.20.0.jar --root-dir .\system_a
```

```text
Project-Root/
  |
  +--- wiremock-standalone-2.20.0.jar
  |
  +--- system_a/
  |     |
  |     +--- __files
  |     +--- mappings
  |
  +--- system_b
        |
        +--- __files
        +--- mappings
```

# Request Matching

## URL matching

次のようなRest APIで{id}の部分に任意の数値をアイテムIDとして指定するリクエストに対してマッピングするには

```text
curl -v http://localhost:8080/item/{id}
```

`urlPattern`にマッチングする正規表現を指定します。この例ではidに3桁までの数値を指定すると下記のマッピングファイルに記述したJSONがレスポンスされます。

```json
{
  "request": {
    "method": "GET",
    "urlPattern": "/item/(\\d{1,3})"
  },
  "response": {
    "status": 200,
    "jsonBody": {
      "id": 100,
      "name": "りんご"
    },
    "headers": {
      "Content-Type": "application/json"
    }
  }
}
```

この例だと4桁以上の数値を指定した場合は次のようなレスポンスが返り、リクエストにマッチするレスポンスがないことがわかります。

```text

                                               Request was not matched
                                               =======================

-----------------------------------------------------------------------------------------------------------------------
| Closest stub                                             | Request                                                  |
-----------------------------------------------------------------------------------------------------------------------
                                                           |
GET                                                        | GET
/item/(\d{1,3})                                            | /item/1000                                          <<<<< URL does not match
                                                           |
                                                           |
-----------------------------------------------------------------------------------------------------------------------
```

idに4桁以上の数値が指定されたときにHTTP Status 404を返すようにするには、次のように`priority`を指定したマッピングファイルを作成します。

```json
{
  "priority": 10,
  "request": {
    "method": "GET",
    "urlPattern": "/item/(\\d)*"
  },
  "response": {
    "status": 404,
    "jsonBody": {
      "errorCode": 999,
      "errorMessage": "アイテムは見つかりませんでした"
    },
    "headers": {
      "Content-Type": "application/json"
    }
  }
}
```

curlでアクセスしてみます。

```text
> curl -v http://localhost:8080/item/1001
*   Trying ::1...
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /item/1001 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.46.0
> Accept: */*
>
< HTTP/1.1 404 Not Found
< Content-Type: application/json
< Matched-Stub-Id: 0d9cf308-6452-4833-904e-6c62c7df4b60
< Transfer-Encoding: chunked
< Server: Jetty(9.2.z-SNAPSHOT)
<
{"errorCode":999,"errorMessage":"アイテムは見つかりませんでした"}* Connection #0 to host localhost left intact
```

## idの値に応じたレスポンス

例えば、idが100のときと101のときでレスポンスするJSONを変えるにはテンプレート機能を利用することで実現できます。
テンプレート機能を有効にするにはWiremock起動時のオプションに`--local-response-templating`を追加します。
下記のテンプレート機能を利用したマッピングファイルのポイントはbodyFileNameの`{{request.path.[1]}}`と、`transformers`です。

Wiremockのテンプレートエンジンはhandlebarsを利用していて`{{request.path.[1]}}`はhandlebarsの記法になります。この記述の[1]は`/item/{id}`というリクエストパスの{id}部分を表しています。([0]はitemです。)
なのでbodyFileNameの"{{request.path.[1]}}.json"は、テンプレートエンジンで解釈されると{id}.jsonとなります。

`transformers`はこのマッピングファイルがテンプレート機能を利用することを宣言します。

```json:200_get_item_by_id_template.json
{
  "request": {
    "method": "GET",
    "urlPattern": "/item/(\\d)*"
  },
  "response": {
    "status": 200,
    "bodyFileName": "{{request.path.[1]}}.json",
    "headers": {
      "Content-Type": "application/json"
    },
    "transformers": [
      "response-template"
    ]
  }
}
```

`__files/100.json`は、`/item/100`というリクエストのときに読まれるjsonになります。

```json:100.json
{
  "id": 100,
  "name": "りんご"
}
```

同様に`__files/101.json`は、`/item/101`というリクエストのときに読まれるjsonになります。

```json:101.json
{
  "id": 101,
  "name": "みかん"
}
```

Wiremockで利用できるリクエスト属性には下記のものが定義されています。

|request attributes                      |
|:---------------------------------------|
|request.url                             |
|request.requestLine.path                |
|request.requestLine.pathSegments.[\<n>] |
|request.requestLine.query.\<key>.[\<n>] |
|request.requestLine.method              |
|request.requestLine.host                |
|request.requestLine.port                |
|request.requestLine.scheme              |
|request.requestLine.baseUrl             |
|request.headers.\<key>.[\<n>]           |
|request.cookies.\<key>.[\<n>]           |
|request.body                            |