JSON/YAMLで定義可能なHTTPモックサーバーMmockを触ってみる(シナリオに合わせて動くステートフルなモックの定義と部分的なプロキシモードの適用)

JSON/YAMLで定義可能なHTTPモックサーバーMmockを触ってみました。 本記事では、「シナリオに合わせて動くステートフルなモックの定義」と「部分的なプロキシモードの適用」機能について試してみました。
2018.08.21

こちらの記事(JSON/YAMLで定義可能なHTTPモックサーバーMmockを触ってみる)で紹介させていただいたHTTPモックサーバー、Mmockですが本記事ではもう少し踏み込んだ利用方法について紹介させていただきます。

本記事は、

  • シナリオに合わせて動くステートフルなモックの定義
  • 部分的なプロキシモードの適用

の各機能を実際にMmockへ適用しながらどう設定するのか何ができるのかを紹介いたします。

Mmock

Mmock | GitHub

MmockはGo言語で書かれたHTTPモックサーバーです。

README.mdでは、主な特徴として以下があげられています。

  • JSONもしくはYAMLでのモック定義
  • レスポンスにフェイク用のデータ or リクエストのデータを利用できる
  • パラメータを含んだパスを定義可能
  • ワイルドカードによるマッチング
  • メソッド、URL、クエリパラメータ、ヘッダ、クッキー、リクエストボディでリクエストをマッチング
  • リスタートせずにモックを編集可能
  • ブラウザでのリクエストおよびログの参照
  • シナリオに沿ったステートフルな振る舞い
  • 部分的なプロキシモードの適用
  • リアルタイムに全てのログを取得するエンドポイントの実装
  • 優先度によるマッチング
  • サーバーエラーをシミュレートするためのcrazy mode
  • Open API Specificationに沿ったスキーマをホスト
  • 軽い、移行しやすい
  • インストール不要

太字は本記事で試した機能、リンクは別の記事で試した機能になります。)

検証環境

  • go version go1.9.4 linux/amd64

シナリオに合わせて動くステートフルなモックの定義

Mmock自体のセットアップについては公式のREADME.mdを参照ください。

また、以下の記事でもセットアップの手順を記載しています。

JSON/YAMLで定義可能なHTTPモックサーバーMmockを触ってみる(セットアップ、シンプルなモックの定義、ブラウザでのアクセスログおよびモック定義一覧の確認まで)

シナリオの定義について

機能についての説明

各モックはscenario.nameというキーにひもづくstateの値を持つことができます。 stateの値は同じscenario.nameを持ったモック間で共有されます。 モックではstateに関する以下の機能を持っています。

  • モックが受け入れるstateを制限
  • リクエストを受けた際にモックで定義した新しいstateへの更新

これにより各モックが必要とするstateを制限し、呼び出し時にstateを更新することで、同一のリクエストに対して状態ごとに異なるレスポンスを行うことができます。 例えば、決められたフローでAPIを実行しないとエラーになるサービスをシミュレートしたい場合にこの機能を利用できます。

設定方法

シナリオはモック定義内のcontrol.scenarioにて定義可能です。

以下の3つのパラメータを指定することで、ステートフルなモックを定義します。

  • name
    • シナリオの名前です。この値がstateのキーとなります。
  • requiredState
    • モックが必要とするstateです。nameにひもづくstateがこの値にマッチしない場合、そのモックはリクエストに対して適用されません。
  • newState
    • このモックが呼び出された後のstateです。

エンドポイントの定義に関わらず、control.scenario.nameが等しいモック間では1つのstateを参照、更新します。 また、マッチするモックが1つも存在しない場合Mmockは404を返却します。

また、stateの初期値はnot_startedです。

実際に設定してみる

以下のような状態遷移をするItemリソースのモックについて考えてみます。

Itemは以下の2つのエンドポイントを持ちます。

  • create(Itemの作成)
  • delete(Itemの削除)

create

createエンドポイントは、「リソースが存在しない場合は202を返す」「リソースが存在する場合は409を返す」とします。

モックの定義は以下のようになります。

post.json(リソースが存在しない場合のモック)
{
    "URI": "post.json",
    "description": "Create new article",
    "request": {
        "method": "POST",
        "path": "/items/:itemid.json"
    },
    "response": {
        "statusCode": 202,
        "headers": {
            "Content-Type": [
                "application/json"
            ]
        }
    },
    "control": {
        "scenario": {
            "name": "item",
            "requiredState": [
                "not_started",
                "deleted"
            ],
            "newState": "exist"
        }
    }
}

requiredStateに指定しているnot_startedは、シナリオの初期状態のstateです。

この定義のみだとリソースが存在する場合に409を返却することができないため、リソースを作成が存在する場合のモックもあわせて定義します。

post_conflict.json(リソースが存在する場合のモック)
{
    "URI": "post.json",
    "description": "Create new article(conflict)",
    "request": {
        "method": "POST",
        "path": "/items/:itemid.json"
    },
    "response": {
        "statusCode": 409,
        "headers": {
            "Content-Type": [
                "application/json"
            ]
        }
    },
    "control": {
        "scenario": {
            "name": "item",
            "requiredState": [
                "exist"
            ]
        }
    }
}

stateがexistの場合はこのモックにマッチさせます。

delete
{
    "URI": "delete.json",
    "description": "Delete item",
    "request": {
        "method": "DELETE",
        "path": "/items/:itemid.json"
    },
    "response": {
        "statusCode": 200,
        "headers": {
            "Content-Type": [
                "application/json"
            ]
        }
    },
    "control": {
        "scenario": {
            "name": "item",
            "requiredState": [
                "exist"
            ],
            "newState": "deleted"
        }
    }
}

リソースが存在しない場合のdeleteは404を返却させます。stateがマッチしない場合Mmockはデフォルトで404を返却するため、ここではエラー用のモックは定義しません。

上記の通りstateはモックのcontrol.scenario.nameにひもづくため、同一のnameに対して複数の状態を管理することができません。

そのため、1つのMmockに対して並列でテストを行うと予期しない状態でのテストが行われる可能性があります。 並行するテストごとに別のMmockを立ち上げるのが良いかと思います。 または、仕組みそのものが複雑になってしまいますがMMockはAPI経由でのモック定義をサポートしているので、テスト時に動的に使い捨てのモックを作成するのがうまくハマるケースもあるかもしれません。

stateのリセット

コンソールが起動しているポートに対してGET /api/scenarios/reset_allを実行すると、全てのstateを初期状態に戻すことができます。

エンドポイントによる操作をせずにAPIから直接stateを変更することも可能です。シナリオに関連するAPIの定義はMMock/Scenarioを参照ください。

部分的なプロキシモードの適用

モックには他のURLへリクエストをプロキシする設定が可能です。 controll.proxyBaseURLにURLを指定すると、Mmockへのリクエストが指定したURLにプロキシされ、指定したURLからのレスポンスがそのままクライアントに返却されます。

この機能によって、例えば連携する外部サービスのホスト名を設定で変更できるアプリケーションのテスト時に、同一のホスト名で実際のサービス接続とモックの利用を切り分けることができます。

プロキシの設定例

以下の設定では、POST /echoへのリクエストをPostman Echoへプロキシし、レスポンスをそのままクライアントに返却します。

{
    "URI": "echo.json",
    "description": "echo by postman",
    "request": {
        "method": "POST",
        "path": "/echo"
    },
    "control": {
        "proxyBaseURL": "https://postman-echo.com/post"
    }
}

まとめ

JSON/YAMLで定義可能なHTTPモックサーバーMmockを触ってみました。

本記事では、「シナリオに合わせて動くステートフルなモックの定義」と「部分的なプロキシモードの適用」機能について試してみました。

「シナリオに合わせて動くステートフルなモックの定義」は、あまり頑張りすぎると管理しきれなくなりそうな感じがしますが、フローの通りに実行する必要のあるエンドポイントのテスト、程度であれば有用なのではないでしょうか。

「部分的なプロキシモードの適用」は、一部のエンドポイントでレートリミットが極端に厳しかったり、課金の発生するエンドポイントだけをモック化したいような場合に便利そうです。

Mmockのセットアップおよび基本的な使い方についてはこちらの記事にて紹介しています。

JSON/YAMLで定義可能なHTTPモックサーバーMmockを触ってみる(セットアップ、シンプルなモックの定義、ブラウザでのアクセスログおよびモック定義一覧の確認まで)

モックサーバーについての記事一覧はこちらをご覧ください。

私からは以上です。