asken テックブログ

askenエンジニアが日々どんなことに取り組み、どんな「学び」を得ているか、よもやま話も織り交ぜつつ綴っていきます。 皆さまにも一緒に学びを楽しんでいただけたら幸いです!

モックサーバ WireMockについて

この記事は、株式会社asken (あすけん) Advent Calendar 2024 の16日目の記事です。

こんにちは、askenの入江です。

今回は、外部APIのモックサーバーとして非常に便利な「WireMock」の使い方をご紹介します。

開発でのよくある課題

現代の開発プロジェクトでは、外部APIや外部システムとの連携が欠かせません。しかし、これらの外部システムを利用する際に、次のような問題が発生することがあります。

  • テストケースの多様性: 外部システムの動作やユーザの過去の操作に合わせて応答パターンを再現するのが難しい。
  • 外部依存の影響: 外部システムがダウンしていると、開発やテストが進まない。
  • 特殊シナリオの再現性: 通信エラーや遅延など、特定のシナリオを意図的に再現するのが困難。

これらの課題を解決する手段のひとつとして、モックサーバを導入することがあげられます。

今回、モックサーバとしてWireMockを使ってみたので、WireMockの導入方法や使い方を紹介します。

モックサーバを知らない人や、モックサーバ導入のために情報収集している人の参考になれば幸いです。

WireMockとは?

WireMockは、HTTPベースのモックサーバーを簡単に構築できるオープンソースツールです。以下のような特徴があります。

特徴 説明
REST APIのモック 任意のリクエストに対するモックレスポンスを簡単に設定可能。
ネットワーク問題の再現 遅延やタイムアウトなど、通信エラーをシミュレートできる。
柔軟な振る舞い クエリパラメータやヘッダー値に応じたレスポンスの分岐が可能。
状態管理 ユーザー操作や状態に応じて応答内容を変化させることが可能。

WireMockの導入

WireMockの導入方法はいくつかありますが、今回はDockerを使用してスタンドアロンモードで動かす方法を解説します。

1. Docker Composeの設定

以下のようなdocker-compose.ymlファイルを作成します。

version: '3'
services:
  wire-mock:
    image: wiremock/wiremock:3.10.0
    ports:
      - "8080:8080"
    volumes:
      - ./__files:/home/wiremock/__files
      - ./mappings:/home/wiremock/mappings

フォルダ構成は次のようにします。

./
├── __files
├── compose.yml
└── mappings

2. 起動と確認

以下のコマンドでWireMockを起動します。

docker-compose up -d

起動後、http://localhost:8080/__admin/swagger-ui/にアクセスすると、管理用のSwagger UIが表示されます。

wiremock_swagger

WireMockの基本的な使い方

ここでは、簡単なモックAPIを作成する方法を説明します。

1. リクエストとレスポンスの基本設定

以下のようなレスポンスを返すモックを作成します。

{
    message: "Hello World!"
}

mappingsディレクトリ配下に次のようなhello.jsonファイルを作成します。

{
  "request": {
    "urlPath": "/hello",
    "method": "POST"
  },
  "response": {
    "status": 200,
    "jsonBody": {
        "message" : "Hello World!"
    }
    ,
    "headers": {
      "Content-Type": "application/json; charset=utf-8"
    }
  }
}

ファイル作成後、WireMockを再起動するか、以下のエンドポイントを実行して設定をリロードします。

POST http://localhost:8080/__admin/mappings/reset

http://localhost:8080/helloを実行してみます。

ブラウザでの確認結果

簡単にモックを作成することができました。

2. レスポンスの外部定義

レスポンスボディが大きくなる場合、__filesディレクトリに外部定義することが可能です。

__files/hello_file.jsonを作成:

{
    "message" : "Hello World! from __files"
} 

mappings/hello.jsonを以下のように修正:

{
    "request": {
      "urlPath": "/hello",
      "method": "GET"
    },
    "response": {
      "status": 200,
      "bodyFileName": "hello_file.json",
      "headers": {
        "Content-Type": "application/json; charset=utf-8"
      }
    }
}

応用的な使い方

1. リクエストパラメータによる動的レスポンス

リクエストにクエリパラメータを含めた場合に、動的なレスポンスを返す例を紹介します。

レスポンスの内容を動的に変更するために、WireMockに追加の起動オプション設定が必要になります。下記をcompose.ymlを追加して起動しなおします。

entrypoint: [ "/docker-entrypoint.sh", "--global-response-templating"]

設定例

以下の設定により、/hello?name=xxxxのようなリクエストに応じて動的なレスポンスを返します。

mappings/dynamic_hello.json を作成:

{
    "request": {
      "urlPath": "/hello",
      "method": "GET",
      "queryParameters": {
        "name": {
          "matches": ".*"
        }
      }
    },
    "response": {
      "status": 200,
      "bodyFileName": "hello_user.json",
      "headers": {
        "Content-Type": "application/json; charset=utf-8"
      }
    }
}

__files/dynamic_hello.json を作成:

{
    "message" : "Hello World! {{request.query.name}}"
} 

/hello?name=hogeにアクセスします。

すると以下のようなレスポンスが返却されるようになります。

{
    "message" : "Hello World! hoge"
} 

2. ネットワーク遅延のシミュレーション

レスポンスを遅延させて、通信の遅さをシミュレーションすることもできます。

mappings/hello.jsonchunkedDribbleDelayを設定:

{
    "request": {
      "urlPath": "/hello",
      "method": "GET"
    },
    "response": {
      "status": 200,
      "bodyFileName": "hello_file.json",
      "headers": {
        "Content-Type": "application/json; charset=utf-8"
      },
      "chunkedDribbleDelay": {
        "numberOfChunks": 5,
        "totalDuration": 1000
      }
    }
}

この設定は、レスポンスの内容を5つのチャンクに分割し、合計で1000msで送信する設定を示しています。

実際に、ブラウザ開発者ツールでNWの遅さをみてみると1秒間かけて通信が行われていることがわかります。

ネットワーク通信の時間

3. 状態を使ったモックの応答変更

WireMockは「シナリオ」と呼ばれる機能を使って、状態が変化するステートフルなモックを設定することができます。

  • シナリオには状態を持たせることができ、その状態に応じてリクエストのレスポンスを柔軟に変更することが可能です。
  • 特定のリクエストやレスポンスの実行時に、シナリオの状態を更新することが可能です。

この機能を利用することで、ユーザー操作によって状態が変化し、それ応じた異なるレスポンスを返却するAPI挙動を再現できます。

例えば、下記のようにWEBページ上で同意を行うと、APIのレスポンスが変わるようなケースです。

シーケンス
このように、現実のユースケースに即した高度なモックを構築することが可能です。

さきほどのシーケンスにおいて、状態によりレスポンス内容が変わるAPI、状態を変更するAPIを図示すると以下のようになります。

シーケンス 色付けver
では、これをもとにしてマッピング定義を作成します。

mappings/statefull.json に 一連のマッピング定義を作成:

{
    "mappings": [
      {
        "scenarioName": "state_example",
        "requiredScenarioState": "Started",
        "request": {
          "urlPath": "/api/status",
          "method": "GET"
        },
        "response": {
          "status": 200,
          "bodyFileName": "status_no.json",
          "headers": {
            "Content-Type": "application/json"
          }
        }
      },
      {
        "scenarioName": "state_example",
        "requiredScenarioState": "AfterAgree",
        "request": {
          "urlPath": "/api/status",
          "method": "GET"
        },
        "response": {
          "status": 200,
          "bodyFileName": "status_yes.json",
          "headers": {
            "Content-Type": "application/json"
          }
        }
      },
      {
        "scenarioName": "state_example",
        "request": {
          "urlPath": "/web/confirm",
          "method": "GET"
        },
        "response": {
          "status": 200,
          "bodyFileName": "confirm.html"
        }
      },
      {
        "scenarioName": "state_example",
        "newScenarioState": "AfterAgree",
        "request": {
          "urlPath": "/web/agree",
          "method": "GET"
        },
        "response": {
          "status": 200,
          "bodyFileName": "agree.html"
        }
      }
    ]
  }

__files/status_no.json 初期状態でモックが返却するレスポンス:

{
    "status": "no"
}

__files/status_yes.json 状態遷移後にモックが返却するレスポンス:

{
    "status": "yes"
}

__files/confirm.html 間に挟むHTML:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>同意確認ページ</title>
</head>
<body>
<div class="container">
    <h1>同意しますか?</h1>
    <a href="/web/agree">はい</a>
</div>
</body>
</html>

__files/agree.html 状態変更するモックが返却するHTML:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>同意確認ページ</title>
</head>
<body>
<div class="container">
    <h1>同意しました</h1>
</div>
</body>
</html>

シナリオは、初期状態が「Started」として設定されています。RequiredScenarioState プロパティに設定された状態に応じてレスポンス内容が変わります。

そして、newScenarioState プロパティと呼ばれる設定に特定のリクエスト後に状態を変更することが可能です。

たとえば上記の例では、次のように振る舞います

  • 状態が「Started」で開始。
  • /api/status にアクセスするとrequiredScenarioState がStartedに設定されている定義のレスポンスを返却。
  • /web/agree にアクセスすると、 newScenarioState の設定に応じて、シナリオの状態が「AfterAgree」に遷移。
  • /api/status にアクセスするとrequiredScenarioState がAfterAgreeに設定されている定義のレスポンスを返却。

このようにシナリオの状態を利用して、ユーザ操作によりレスポンスを動的に切り替えることを実現します。

実際に動かして定義を確認してみます。

最初は/api/statusにアクセスすると下記のレスポンスが返却されます。

{
    "status": "no"
}

/web/confirmにアクセスします

同意確認画面

そのまま「はい」をクリックします。

同意後画面

/web/agreeにリクエストが送信され、このタイミングでシナリオの状態が「Started」から「AfterAgree」に遷移します。

この段階で/api/statusにアクセスすると下記のレスポンスが返却されるようになります。

{
    "status": "yes"
}

このような複雑なケースもWireMockで実現することができます。

なお状態をリセットにするには/__admin/scenarios/reset を使います。

まとめ

WireMockのよくある使いかたをまとめてみました。WireMockを活用することで、以下のような開発課題を解決できます。

  • 外部依存を排除: 開発環境を外部システムの状況に依存させない。
  • テストケースの柔軟性向上: 様々なシナリオを簡単に再現可能。
  • チームの効率化: 特殊な条件下でのテストを効率的に行える。

この記事が、開発現場でのモック利用の参考になれば幸いです。

参考

WireMock公式サイト : https://wiremock.org/

積極採用中です

askenではエンジニアを絶賛募集中です。

まずはカジュアルにお話しできればと思いますので、ぜひお気軽にご連絡ください!

募集一覧

wantedly