MountebankとNode.jsを使用してサービスをモックする方法

著者は、Write for DOnationsプログラムの一部として寄付を受け取るためにOpen Internet/Free Speech Fundを選択しました。

前書き

複雑なservice-oriented architectures (SOA)では、プログラムが特定のワークフローを実行するために複数のサービスを呼び出す必要があることがよくあります。 すべてが整ったらこれで問題ありませんが、作業中のコードにまだ開発中のサービスが必要な場合は、他のチームが作業を完了するまで待ってから作業を開始できます。 さらに、テスト目的で、天気APIや記録管理システムなどの外部ベンダーサービスと対話する必要がある場合があります。 ベンダーは通常、必要な数の環境を提供しておらず、多くの場合、システム上のテストデータを簡単に制御できません。 このような状況では、未完成のサービスや管理外のサービスにより、コードテストがイライラする可能性があります。

これらすべての問題の解決策は、service mockを作成することです。 サービスモックは、最終製品で使用するサービスをシミュレートするコードですが、実稼働で使用する実際のサービスよりも軽量で、複雑さが少なく、制御が容易です。 デフォルトの応答または特定のテストデータを返すように模擬サービスを設定し、依存サービスが実際に存在するかのように、テストに関心のあるソフトウェアを実行できます。 このため、サービスをモックする柔軟な方法があると、ワークフローをより速く、より効率的にすることができます。

エンタープライズ環境では、モックサービスの作成はservice virtualizationと呼ばれることがあります。 多くの場合、サービスの仮想化は高価なエンタープライズツールに関連付けられていますが、サービスのモックに高価なツールは必要ありません。 Mountebankは、RESTおよびSOAPサービスを含むHTTPサービスをモックするために使用できる無料のオープンソースサービスモックツールです。 これを使用して、SMTPまたはTCPリクエストをモックすることもできます。

このガイドでは、Node.jsとMountebankを使用して2つの柔軟なサービスモックアプリケーションを構築します。 両方のモックサービスは、HTTPでRESTリクエストの特定のポートをリッスンします。 この単純なモック動作に加えて、サービスはcomma-separated values (CSV) fileからモックデータも取得します。 このチュートリアルの後、あらゆる種類のサービス動作をモックできるようになるため、アプリケーションをより簡単に開発およびテストできます。

前提条件

このチュートリアルを実行するには、次のものが必要です。

[[step-1 -—- starting-a-node-js-application]] ==ステップ1—Node.jsアプリケーションの開始

この手順では、Mountebankインスタンスのベースとして機能する基本的なNode.jsアプリケーションと、後の手順で作成する模擬サービスを作成します。

[。注意]##

Note: Mountebankは、コマンドnpm install -g mountebankを使用してグローバルにインストールすることにより、スタンドアロンアプリケーションとして使用できます。 次に、mbコマンドで実行し、RESTリクエストを使用してモックを追加できます。

これはMountebankを起動して実行する最速の方法ですが、Mountebankアプリケーションを自分で作成すると、アプリの起動時に定義済みのモックのセットを実行でき、ソース管理に保存してチームと共有できます。 このチュートリアルでは、これを利用するためにMountebankアプリケーションを手動でビルドします。

まず、アプリケーションを配置する新しいディレクトリを作成します。 好きな名前を付けることができますが、このチュートリアルでは、appという名前を付けます。

mkdir app

次のコマンドを使用して、新しく作成したディレクトリに移動します。

cd app

新しいNode.jsアプリケーションを開始するには、npm initを実行し、プロンプトに入力します。

npm init

これらのプロンプトからのデータは、package.jsonファイルに入力するために使用されます。このファイルには、アプリケーションとは何か、アプリケーションが依存するパッケージ、および使用するさまざまなスクリプトが記述されています。 Node.jsアプリケーションでは、スクリプトはアプリケーションをビルド、実行、およびテストするコマンドを定義します。 プロンプトのデフォルトを使用するか、パッケージ名、バージョン番号などを入力できます。

このコマンドを完了すると、package.jsonファイルを含む基本的なNode.jsアプリケーションが作成されます。

次を使用して、Mountebank npmパッケージをインストールします。

npm install -save mountebank

このコマンドは、Mountebankパッケージを取得し、アプリケーションにインストールします。 Mountebankを依存関係として使用してpackage.jsonファイルを更新するには、必ず-saveフラグを使用してください。

次に、コマンドnode src/index.jsを実行する開始スクリプトをpackage.jsonに追加します。 このスクリプトは、アプリのエントリポイントをindex.jsとして定義します。これは、後の手順で作成します。

テキストエディタでpackage.jsonを開きます。 任意のテキストエディタを使用できますが、このチュートリアルではnanoを使用します。

nano package.json

"scripts"セクションに移動し、行"start": "node src/index.js"を追加します。 これにより、アプリケーションを実行するためのstartコマンドが追加されます。

最初のプロンプトの入力方法に応じて、package.jsonファイルは次のようになります。

app/package.json

{
  "name": "diy-service-virtualization",
  "version": "1.0.0",
  "description": "An application to mock services.",
  "main": "index.js",
  "scripts": {
    "start": "node src/index.js"
  },
  "author": "Dustin Ewers",
  "license": "MIT",
  "dependencies": {
    "mountebank": "^2.0.0"
  }
}

これで、アプリを作成し、Mountebankをインストールし、起動スクリプトを追加して作成したMountebankアプリケーションのベースができました。 次に、アプリケーション固有の設定を保存する設定ファイルを追加します。

[[step-2 -—- creating-a-settings-file]] ==ステップ2—設定ファイルの作成

この手順では、Mountebankインスタンスと2つの模擬サービスがリッスンするポートを決定する設定ファイルを作成します。

Mountebankまたはモックサービスのインスタンスを実行するたびに、そのサービスを実行するネットワークポートを指定する必要があります(例:http://localhost:5000/)。 これらを設定ファイルに置くことにより、アプリケーションの他の部分は、サービスとMountebankインスタンスのポート番号を知る必要があるときはいつでもこれらの設定をインポートできます。 これらを定数としてアプリケーションに直接コーディングすることもできますが、ファイルに保存すると、後で設定を変更する方が簡単になります。 この方法では、1つの場所で値を変更するだけで済みます。

appディレクトリからsrcというディレクトリを作成することから始めます。

mkdir src

作成したフォルダーに移動します。

cd src

settings.jsというファイルを作成し、テキストエディタで開きます。

nano settings.js

次に、メインのMountebankインスタンスと、後で作成する2つの模擬サービスのポートの設定を追加します。

app/src/settings.js

module.exports = {
    port: 5000,
    hello_service_port: 5001,
    customer_service_port: 5002
}

この設定ファイルには3つのエントリがあります。port: 5000はポート5000をメインのMountebankインスタンスに割り当て、hello_service_port: 5001はポート5001を後で作成するHello Worldテストサービスに割り当てます。ステップし、customer_service_port: 5002はポート5002をCSVデータで応答するモックサービスアプリに割り当てます。 ここのポートが占有されている場合は、自由に変更してください。 module.exports =を使用すると、他のファイルでこれらの設定をインポートできます。

このステップでは、settings.jsを使用して、Mountebankとモックサービスがリッスンするポートを定義し、これらの設定をアプリの他の部分で利用できるようにしました。 次の手順では、これらの設定を使用して初期化スクリプトを作成し、Mountebankを起動します。

[[step-3 -—- building-the-initialization-script]] ==ステップ3—初期化スクリプトの構築

この手順では、Mountebankのインスタンスを開始するファイルを作成します。 このファイルはアプリケーションのエントリポイントになります。つまり、アプリを実行すると、このスクリプトが最初に実行されます。 新しいサービスモックを作成するときに、このファイルに行を追加します。

srcディレクトリから、index.jsというファイルを作成し、テキストエディタで開きます。

nano index.js

前の手順で作成したsettings.jsファイルで指定されたポートで実行されるMountebankのインスタンスを開始するには、次のコードをファイルに追加します。

app/src/index.js

const mb = require('mountebank');
const settings = require('./settings');

const mbServerInstance = mb.create({
        port: settings.port,
        pidfile: '../mb.pid',
        logfile: '../mb.log',
        protofile: '../protofile.json',
        ipWhitelist: ['*']
    });

このコードは3つのことを行います。 まず、以前にインストールしたMountebank npmパッケージ(const mb = require('mountebank');)をインポートします。 次に、前の手順で作成した設定モジュール(const settings = require('./settings');)をインポートします。 最後に、mb.create()を使用してMountebankサーバーのインスタンスを作成します。

サーバーは、設定ファイルで指定されたポートでリッスンします。 pidfilelogfile、およびprotofileパラメータは、Mountebankが内部でプロセスIDを記録し、ログを保持する場所を指定し、カスタムプロトコル実装をロードするようにファイルを設定するために使用するファイル用です。 ipWhitelist設定は、Mountebankサーバーとの通信を許可するIPアドレスを指定します。 この場合、任意のIPアドレスにそれを開いています。

ファイルを保存して終了します。

このファイルを配置したら、次のコマンドを入力してアプリケーションを実行します。

npm start

コマンドプロンプトが消え、以下が表示されます。

info: [mb:5000] mountebank v2.0.0 now taking orders - point your browser to http://localhost:5000/ for help

これは、アプリケーションが開いていて、リクエストを受け取る準備ができていることを意味します。

次に、進捗状況を確認します。 新しいターミナルウィンドウを開き、curlを使用して次のGET要求をMountebankサーバーに送信します。

curl http://localhost:5000/

これにより、次のJSON応答が返されます。

Output{
    "_links": {
        "imposters": {
            "href": "http://localhost:5000/imposters"
        },
        "config": {
            "href": "http://localhost:5000/config"
        },
        "logs": {
            "href": "http://localhost:5000/logs"
        }
    }
}

Mountebankが返すJSONは、Mountebankでオブジェクトを追加または削除するために使用できる3つの異なるエンドポイントを記述しています。 curlを使用してこれらのエンドポイントにリクエストを送信することにより、Mountebankインスタンスと対話できます。

完了したら、最初のターミナルウィンドウに戻り、CTRL +Cを使用してアプリケーションを終了します。 これによりNode.jsアプリが終了し、追加を続けることができます。

これで、Mountebankのインスタンスを正常に実行するアプリケーションができました。 次のステップでは、REST要求を使用してMountebankアプリケーションにモックサービスを追加するMountebankクライアントを作成します。

[[step-4 -—- building-a-mountebank-client]] ==ステップ4—Mountebankクライアントの構築

Mountebankは、REST APIを使用して通信します。 最後の手順で説明したさまざまなエンドポイントにHTTPリクエストを送信することにより、Mountebankインスタンスのリソースを管理できます。 モックサービスを追加するには、HTTPPOSTリクエストを詐欺師のエンドポイントに送信します。 imposterは、Mountebankのモックサービスの名前です。 偽者は、モックで必要な動作に応じて、単純な場合も複雑な場合もあります。

このステップでは、Mountebankクライアントを構築して、POST要求をMountebankサービスに自動的に送信します。 curlまたはPostmanを使用してPOSTリクエストを詐欺師エンドポイントに送信できますが、テストサーバーを再起動するたびに同じリクエストを送信する必要があります。 複数のモックを使用してサンプルAPIを実行している場合、これを行うクライアントスクリプトを記述する方が効率的です。

node-fetchライブラリをインストールすることから始めます。

npm install -save node-fetch

node-fetch libraryは、JavaScript Fetch APIの実装を提供します。これを使用して、より短いHTTPリクエストを作成できます。 標準のhttpライブラリを使用できますが、node-fetchを使用する方が軽量なソリューションです。

次に、Mountebankにリクエストを送信するクライアントモジュールを作成します。 詐欺師を投稿する必要があるだけなので、このモジュールには1つのメソッドがあります。

nanoを使用して、mountebank-helper.jsというファイルを作成します。

nano mountebank-helper.js

クライアントを設定するには、次のコードをファイルに追加します。

app/src/mountebank-helper.js

const fetch = require('node-fetch');
const settings = require('./settings');

function postImposter(body) {
    const url = `http://127.0.0.1:${settings.port}/imposters`;

    return fetch(url, {
                    method:'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify(body)
                });
}

module.exports = { postImposter };

このコードは、node-fetchライブラリと設定ファイルをプルすることから始まります。 次に、このモジュールは、サービスモックをMountebankに投稿するpostImposterという関数を公開します。 次に、body:は、関数がJavaScriptオブジェクトであるJSON.stringify(body)を受け取ることを決定します。 このオブジェクトは、Mountebankサービスに対してPOSTに送信するものです。 このメソッドはローカルで実行されているため、127.0.0.1localhost)に対してリクエストを実行します。 fetchメソッドは、パラメーターで送信されたオブジェクトを受け取り、POST要求をurlに送信します。

このステップでは、Mountebankクライアントを作成して、新しい模擬サービスをMountebankサーバーに投稿しました。 次のステップでは、このクライアントを使用して最初の模擬サービスを作成します。

[[step-5 -—- creating-your-first-mock-service]] ==ステップ5—最初のモックサービスを作成する

前の手順では、Mountebankサーバーを作成するアプリケーションと、そのサーバーを呼び出すコードを作成しました。 次は、そのコードを使用して詐欺師または模擬サービスを作成します。

Mountebankでは、各詐欺師にはstubsが含まれています。 スタブは、詐欺師が与える応答を決定する構成セットです。 スタブは、predicates and responsesの組み合わせにさらに分割できます。 述語は、詐欺師の応答をトリガーするルールです。 述語は、URL、リクエストコンテンツ(XMLまたはJSONを使用)、HTTPメソッドなど、さまざまな種類の情報を使用できます。

Model-View-Controller (MVC)アプリの観点から見ると、詐欺師はコントローラーのように機能し、スタブはそのコントローラー内のアクションのように機能します。 述語は、特定のコントローラーアクションを指すルーティングルールです。

最初のモックサービスを作成するには、hello-service.jsというファイルを作成します。 このファイルには、模擬サービスの定義が含まれます。

テキストエディタでhello-service.jsを開きます。

nano hello-service.js

その後、次のコードを追加します。

app/src/hello-service.js

const mbHelper = require('./mountebank-helper');
const settings = require('./settings');

function addService() {
    const response = { message: "hello world" }

    const stubs = [
        {
            predicates: [ {
                equals: {
                    method: "GET",
                    "path": "/"
                }
            }],
            responses: [
                {
                    is: {
                        statusCode: 200,
                        headers: {
                            "Content-Type": "application/json"
                        },
                        body: JSON.stringify(response)
                    }
                }
            ]
        }
    ];

    const imposter = {
        port: settings.hello_service_port,
        protocol: 'http',
        stubs: stubs
    };

    return mbHelper.postImposter(imposter);
}

module.exports = { addService };

このコードは、述語と応答を含む単一のスタブを持つ詐欺師を定義します。 次に、そのオブジェクトをMountebankサーバーに送信します。 このコードは、GETリクエストをルートurlにリッスンし、取得すると{ message: "hello world" }を返す新しいモックサービスを追加します。

上記のコードが作成するaddService()関数を見てみましょう。 まず、応答メッセージhello worldを定義します。

    const response = { message: "hello world" }
...

次に、スタブを定義します。

...
        const stubs = [
        {
            predicates: [ {
                equals: {
                    method: "GET",
                    "path": "/"
                }
            }],
            responses: [
                {
                    is: {
                        statusCode: 200,
                        headers: {
                            "Content-Type": "application/json"
                        },
                        body: JSON.stringify(response)
                    }
                }
            ]
        }
    ];
...

このスタブには2つの部分があります。 述語部分は、ルート(/)URLへのGET要求を探しています。 これは、誰かがGET要求をモックサービスのルートURLに送信すると、stubsが応答を返すことを意味します。 スタブの2番目の部分は、responses配列です。 この場合、1つの応答があり、HTTPステータスコードが200のJSON結果を返します。

最後のステップでは、そのスタブを含む詐欺師を定義します。

...
    const imposter = {
        port: settings.hello_service_port,
        protocol: 'http',
        stubs: stubs
    };
...

これは、/impostersエンドポイントに送信して、単一のエンドポイントでサービスをモックする詐欺師を作成するオブジェクトです。 上記のコードは、設定ファイルで決定したポートにportを設定し、protocolをHTTPに設定し、stubsを詐欺師のスタブとして割り当てることにより、詐欺師を定義します。

モックサービスができたので、コードはそれをMountebankサーバーに送信します。

...
    return mbHelper.postImposter(imposter);
...

前述のように、MountebankはREST APIを使用してオブジェクトを管理します。 上記のコードは、前に定義したpostImposter()関数を使用して、POST要求をサーバーに送信し、サービスをアクティブ化します。

hello-service.jsを使い終わったら、保存してファイルを終了します。

次に、新しく作成されたaddService()関数をindex.jsで呼び出します。 テキストエディターでファイルを開きます。

nano index.js

Mountebankインスタンスの作成時に関数が呼び出されるようにするには、次の強調表示された行を追加します。

app/src/index.js

const mb = require('mountebank');
const settings = require('./settings');
const helloService = require('./hello-service');

const mbServerInstance = mb.create({
        port: settings.port,
        pidfile: '../mb.pid',
        logfile: '../mb.log',
        protofile: '../protofile.json',
        ipWhitelist: ['*']
    });

mbServerInstance.then(function() {
    helloService.addService();
});

Mountebankインスタンスが作成されると、promiseが返されます。 約束は、後で値を決定しないオブジェクトです。 これは、非同期関数呼び出しを簡素化するために使用できます。 上記のコードでは、.then(function(){...})関数は、Mountebankサーバーが初期化されるときに実行されます。これは、promiseが解決されるときに発生します。

index.jsを保存して終了します。

Mountebankの初期化時にモックサービスが作成されることをテストするには、アプリケーションを起動します。

npm start

Node.jsプロセスがターミナルを占有するため、新しいターミナルウィンドウを開き、GETリクエストをhttp://localhost:5001/に送信します。

curl http://localhost:5001

サービスが機能していることを示す次の応答を受け取ります。

Output{"message": "hello world"}

アプリケーションをテストしたので、最初のターミナルウィンドウに戻り、CTRL +Cを使用してNode.jsアプリケーションを終了します。

このステップでは、最初の模擬サービスを作成しました。 これは、GET要求に応答してhello worldを返すテストサービスモックです。 このモックはデモンストレーションを目的としています。小さなExpressアプリケーションを作成しても得られなかったものは、実際には提供されません。 次のステップでは、Mountebankの機能のいくつかを活用する、より複雑なモックを作成します。

[[step-6 -—- building-a-data-backed-mock-service]] ==ステップ6— Data-Backed MockServiceの構築

前の手順で作成したサービスの種類は一部のシナリオでは問題ありませんが、ほとんどのテストではより複雑な一連の応答が必要です。 この手順では、URLからパラメーターを取得し、それを使用してCSVファイルのレコードを検索するサービスを作成します。

まず、メインのappディレクトリに戻ります。

cd ~/app

dataというフォルダーを作成します。

mkdir data

customers.csvという顧客データのファイルを開きます。

nano data/customers.csv

次のテストデータを追加して、モックサービスに取得するものがあるようにします。

app/data/customers.csv

id,first_name,last_name,email,favorite_color
1,Erda,Birkin,[email protected],Aquamarine
2,Cherey,Endacott,[email protected],Fuscia
3,Shalom,Westoff,[email protected],Red
4,Jo,Goulborne,[email protected],Red

これは、APIモックツールMockarooによって生成された偽の顧客データであり、サービス自体の顧客テーブルにロードする偽のデータに似ています。

ファイルを保存して終了します。

次に、srcディレクトリにcustomer-service.jsという新しいモジュールを作成します。

nano src/customer-service.js

/customers/エンドポイントでGETリクエストをリッスンする詐欺師を作成するには、次のコードを追加します。

app/src/customer-service.js

const mbHelper = require('./mountebank-helper');
const settings = require('./settings');

function addService() {
    const stubs = [
        {
            predicates: [{
                and: [
                    { equals: { method: "GET" } },
                    { startsWith: { "path": "/customers/" } }
                ]
            }],
            responses: [
                {
                    is: {
                        statusCode: 200,
                        headers: {
                            "Content-Type": "application/json"
                        },
                        body: '{ "firstName": "${row}[first_name]", "lastName": "${row}[last_name]", "favColor": "${row}[favorite_color]" }'
                    },
                    _behaviors: {
                        lookup: [
                            {
                                "key": {
                                  "from": "path",
                                  "using": { "method": "regex", "selector": "/customers/(.*)$" },
                                  "index": 1
                                },
                                "fromDataSource": {
                                  "csv": {
                                    "path": "data/customers.csv",
                                    "keyColumn": "id"
                                  }
                                },
                                "into": "${row}"
                              }
                        ]
                    }
                }
            ]
        }
    ];

    const imposter = {
        port: settings.customer_service_port,
        protocol: 'http',
        stubs: stubs
    };

    return mbHelper.postImposter(imposter);
}

module.exports = { addService };

このコードは、customers/<id>のURL形式でGET要求を検索するサービスモックを定義します。 リクエストを受信すると、URLに顧客のidを照会し、CSVファイルから対応するレコードを返します。

このコードは、前のステップで作成したhelloサービスよりもいくつかのMountebank機能を使用します。 まず、behaviorsと呼ばれるMountebankの機能を使用します。 ビヘイビアは、スタブに機能を追加する方法です。 この場合、lookup動作を使用して、CSVファイル内のレコードを検索しています。

...
  _behaviors: {
      lookup: [
          {
              "key": {
                "from": "path",
                "using": { "method": "regex", "selector": "/customers/(.*)$" },
                "index": 1
              },
              "fromDataSource": {
                "csv": {
                  "path": "data/customers.csv",
                  "keyColumn": "id"
                }
              },
              "into": "${row}"
            }
      ]
  }
...

keyプロパティは、正規表現を使用して着信パスを解析します。 この場合、URLのcustomers/の後に続くidを使用しています。

fromDataSourceプロパティは、テストデータの保存に使用しているファイルを指します。

intoプロパティは、結果を変数${row}に挿入します。 その変数は、次のbodyセクションで参照されます。

...
  is: {
      statusCode: 200,
      headers: {
          "Content-Type": "application/json"
      },
      body: '{ "firstName": "${row}[first_name]", "lastName": "${row}[last_name]", "favColor": "${row}[favorite_color]" }'
  },
...

行変数は、応答の本文を設定するために使用されます。 この場合、顧客データを含むJSON文字列です。

ファイルを保存して終了します。

次に、index.jsを開いて、新しいサービスモックを初期化関数に追加します。

nano src/index.js

ハイライトされた行を追加します。

app/src/index.js

const mb = require('mountebank');
const settings = require('./settings');
const helloService = require('./hello-service');
const customerService = require('./customer-service');

const mbServerInstance = mb.create({
        port: settings.port,
        pidfile: '../mb.pid',
        logfile: '../mb.log',
        protofile: '../protofile.json',
        ipWhitelist: ['*']
    });

mbServerInstance.then(function() {
    helloService.addService();
    customerService.addService();
});

ファイルを保存して終了します。

ここで、npm startを使用してMountebankを起動します。 これによりプロンプトが非表示になるため、別のターミナルウィンドウを開きます。 GETリクエストをlocalhost:5002/customers/3に送信して、サービスをテストします。 これにより、id3の下の顧客情報が検索されます。

curl localhost:5002/customers/3

次の応答が表示されます。

Output{
    "firstName": "Shalom",
    "lastName": "Westoff",
    "favColor": "Red"
}

この手順では、CSVファイルからデータを読み取り、JSON応答として返すモックサービスを作成しました。 ここから、テストする必要があるサービスに一致する、より複雑なモックの構築を続けることができます。

結論

この記事では、MountebankとNode.jsを使用して独自のサービスモックアプリケーションを作成しました。 これで、模擬サービスを構築し、チームと共有できます。 テストする必要があるベンダーサービスを含む複雑なシナリオであっても、他のチームの作業が完了するのを待っている間の単純なモックであっても、モックサービスを作成してチームを動かし続けることができます。

Mountebankについて詳しく知りたい場合は、documentationを確認してください。 このアプリケーションをコンテナ化する場合は、Containerizing a Node.js Application for Development With Docker Composeを確認してください。 このアプリケーションを本番環境のような環境で実行する場合は、How To Set Up a Node.js Application for Production on Ubuntu 18.04を確認してください。

Related