如何使用 Mountebank 和 Node.js 模拟服务

作者选择了 开放式互联网 / 自由言论基金作为 写给捐赠计划的一部分接受捐款。

介绍

在复杂的 服务导向架构 (SOA)中,程序通常需要调用多个服务来运行一个特定的工作流程. 一旦一切都做好了,但如果您正在开发的代码需要一个仍在开发的服务,您可能会被困在等待其他团队在开始工作之前完成工作。此外,为测试目的,您可能需要与外部供应商服务(如天气API或记录系统)进行交互。

所有这些问题的解决方案是创建一个 service mock. 服务 mock 是模拟你在最终产品中使用的服务的代码,但它比你在生产中使用的实际服务更轻,更不复杂,更容易控制。

在企业设置中,制作模仿服务有时被称为 service virtualization. 服务虚拟化通常与昂贵的企业工具相关联,但您不需要昂贵的工具来模仿服务。

在本指南中,您将使用 Node.js和Mountebank构建两种灵活的服务模仿应用程序。这两个模仿服务将倾听 HTTP 中的 REST 请求的特定端口。除了这种简单的模仿行为外,该服务还将从 comma-separated values (CSV) 文件中获取模仿数据。经过本教程,您将能够模仿各种服务行为,以便您更容易地开发和测试您的应用程序。

前提条件

要遵循本教程,您将需要以下内容:

步骤 1 — 启动 Node.js 应用程序

在此步骤中,您将创建一个基本的 Node.js 应用程序,该应用程序将作为您的 Mountebank 实例的基础,以及您将在以后的步骤中创建的模仿服务。

<$>[注] **注:**Mountebank可以通过使用命令 npm install -g mountebank在全球范围内安装,作为独立应用程序使用。

虽然这是获得Mountebank并运行的最快方法,但建立Mountebank应用程序本身允许您在应用程序启动时运行一组预定义的笑话,然后您可以在源控制中存储并与您的团队共享。

首先,创建一个新的目录,将您的应用程序放入其中. 你可以随心所欲地命名它,但在本教程中,我们将命名它为应用程序:

1mkdir app

移动到新创建的目录,使用以下命令:

1cd app

要启动一个新的 Node.js 应用程序,运行npm init并填写提示:

1npm init

这些提示的数据将用于填写您的package.json文件,它描述了您的应用程序是什么,它依赖哪些包,以及它使用的不同脚本。在Node.js应用程序中,脚本定义了构建,运行和测试您的应用程序的命令。

完成此命令后,您将拥有一个基本的 Node.js 应用程序,包括 package.json 文件。

现在使用以下方式安装Mountebank npm包:

1npm install -save mountebank

这个命令会抓住Mountebank包并将其安装到您的应用程序中. 请确保使用保存旗帜以更新您的package.json文件,并使用Mountebank作为依赖。

接下来,将一个开始脚本添加到package.json,该脚本运行命令node src/index.js

在文本编辑器中打开package.json。你可以使用你想要的任何文本编辑器,但本教程将使用纳米。

1nano package.json

导航到脚本部分并添加行开始:节点 src/index.js 这将添加一个开始命令来运行您的应用程序。

您的 package.json 文件应该看起来类似于此,取决于您如何填写最初的提示:

 1[label app/package.json]
 2{
 3  "name": "diy-service-virtualization",
 4  "version": "1.0.0",
 5  "description": "An application to mock services.",
 6  "main": "index.js",
 7  "scripts": {
 8    "start": "node src/index.js"
 9  },
10  "author": "Dustin Ewers",
11  "license": "MIT",
12  "dependencies": {
13    "mountebank": "^2.0.0"
14  }
15}

您现在有Mountebank应用程序的基础,您通过创建应用程序,安装Mountebank并添加启动脚本来构建它,接下来,您将添加设置文件来存储应用程序特定的设置。

步骤 2 - 创建设置文件

在此步骤中,您将创建一个设置文件,确定Mountebank实例的哪个端口和两个模仿服务将听到。

每次运行 Mountebank 或模仿服务的实例时,您需要指定该服务将运行在哪个网络端口上(例如, http://localhost:5000/)。通过将这些实例放入设置文件中,您的应用程序的其他部分将能够在需要知道服务和 Mountebank 实例的端口号时导入这些设置。

从你的app目录中创建一个名为src的目录:

1mkdir src

导航到您刚刚创建的文件夹:

1cd src

创建一个名为settings.js的文件,并在文本编辑器中打开它:

1nano settings.js

接下来,添加主 Mountebank 实例和您稍后创建的两个模仿服务的端口设置:

1[label app/src/settings.js]
2module.exports = {
3    port: 5000,
4    hello_service_port: 5001,
5    customer_service_port: 5002
6}

此设置文件有三个条目: port: 5000 为 Mountebank 主实例分配端口 5000, hello_service_port: 5001 为您在下一步创建的 Hello World 测试服务分配端口 5001,而 customer_service_port: 5002 将端口 5002 分配给响应 CSV 数据的模仿服务应用程序。

在此步骤中,您使用settings.js来定义Mountebank和您的模仿服务会收听的端口,并将这些设置提供给您的应用程序的其他部分。

步骤 3 – 构建初始化脚本

在此步骤中,您将创建一个开始 Mountebank 实例的文件. 此文件将是应用程序的入口点,这意味着当您运行应用程序时,此脚本将首先运行。

src目录中创建一个名为index.js的文件,并在文本编辑器中打开它:

1nano index.js

要启动在上一步创建的 settings.js 文件中指定的端口运行的 Mountebank 实例,请将下列代码添加到该文件中:

 1[label app/src/index.js]
 2const mb = require('mountebank');
 3const settings = require('./settings');
 4
 5const mbServerInstance = mb.create({
 6        port: settings.port,
 7        pidfile: '../mb.pid',
 8        logfile: '../mb.log',
 9        protofile: '../protofile.json',
10        ipWhitelist: ['*']
11    });

此代码可以做三件事:首先,它导入你之前安装的 Mountebank npm 包(const mb = require('mountebank');)。然后,它导入你在上一步创建的设置模块(const settings = require('./settings');)。

服务器会听取设置文件中指定的端口。pidfilelogfileprotofile参数是用于Mountebank内部使用的文件来记录其流程ID,指定其存储日志的位置,并设置一个文件来加载自定义协议实现。ipWhitelist设置指明哪些IP地址可以与Mountebank服务器进行通信。

保存和退出文件。

该文件在位置后,输入以下命令来运行您的应用程序:

1npm start

命令提示将消失,您将看到以下内容:

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

这意味着您的应用程序是开放的,并准备接受请求。

接下来,检查你的进度. 打开一个新的终端窗口,并使用弯曲将以下GET请求发送到Mountebank服务器:

1curl http://localhost:5000/

将返回以下 JSON 响应:

 1[secondary_label Output]
 2{
 3    "_links": {
 4        "imposters": {
 5            "href": "http://localhost:5000/imposters"
 6        },
 7        "config": {
 8            "href": "http://localhost:5000/config"
 9        },
10        "logs": {
11            "href": "http://localhost:5000/logs"
12        }
13    }
14}

Mountebank 返回的 JSON 描述了您可以在 Mountebank 中添加或删除对象的三个不同的终端点。

当你完成时,回到你的第一个终端窗口,并使用CTRL +C离开应用程序,这将退出你的Node.js应用程序,所以你可以继续添加到它。

现在你有一个成功运行 Mountebank 实例的应用程序. 在下一步,你将创建一个 Mountebank 客户端,该客户端使用 REST 请求将模仿服务添加到你的 Mountebank 应用程序中。

步骤 4 – 建立一个Mountebank客户端

Mountebank 使用 REST API 进行通信. 您可以通过将 HTTP 请求发送到最后一步中提到的不同终端来管理 Mountebank 实例的资源. 要添加模仿服务,您可以向欺骗者终端发送 HTTP POST 请求. 一个 imposter 是 Mountebank 中的模仿服务的名称. 模仿者可以简单或复杂,取决于您希望在模仿中的行为。

在此步骤中,您将构建一个Mountebank客户端,自动向Mountebank服务发送POST请求. 您可以使用curlPostman发送POST请求到骗子终端,但您每次重新启动测试服务器时都需要发送相同的请求。

开始安装node-fetch库:

1npm install -save node-fetch

node-fetch 图书馆为您提供了 JavaScript Fetch API 的实现,您可以使用它来编写更短的 HTTP 请求。

现在,创建一个客户端模块,向Mountebank发送请求. 你只需要发布骗子,所以这个模块将有一个方法。

使用nano创建一个名为mountebank-helper.js的文件:

1nano mountebank-helper.js

要设置客户端,请在文件中插入以下代码:

 1[label app/src/mountebank-helper.js]
 2const fetch = require('node-fetch');
 3const settings = require('./settings');
 4
 5function postImposter(body) {
 6    const url = `http://127.0.0.1:${settings.port}/imposters`;
 7
 8    return fetch(url, {
 9                    method:'POST',
10                    headers: { 'Content-Type': 'application/json' },
11                    body: JSON.stringify(body)
12                });
13}
14
15module.exports = { postImposter };

此代码始于拖入node-fetch库和您的设置文件。此模块然后暴露一个名为postImposter的函数,该函数向Mountebank发送服务笑话。接下来,body:决定该函数采用JSON.stringify(body),一个JavaScript对象。该对象是您将POST发送给Mountebank服务的内容。由于此方法在本地运行,您会对127.0.0.1(localhost)运行您的请求。

在此步骤中,您创建了一个Mountebank客户端,将新的模仿服务发布到Mountebank服务器上,在下一步,您将使用该客户端创建您的第一个模仿服务。

步骤5 - 创建您的第一个模特服务

在之前的步骤中,您构建了一个应用程序,创建了一个Mountebank服务器和代码来调用该服务器,现在是时候使用该代码来构建一个骗子或模仿服务了。

在Mountebank中,每个欺骗者都包含了 stubs。欺骗者是定义欺骗者会给出的答案的配置集。欺骗者可以进一步分为 predicatesresponses的组合。先例是触发欺骗者响应的规则。欺骗者可以使用许多不同类型的信息,包括URL、请求内容(使用XML或JSON)和HTTP方法。

Model-View-Controller (MVC)应用程序的观点来看,一个欺骗者像控制器一样行事,而像控制器内部的行动。

要创建你的第一个模仿服务,创建一个名为hello-service.js的文件,该文件将包含你的模仿服务的定义。

在文本编辑器中打开hello-service.js:

1nano hello-service.js

然后添加以下代码:

 1[label app/src/hello-service.js]
 2const mbHelper = require('./mountebank-helper');
 3const settings = require('./settings');
 4
 5function addService() {
 6    const response = { message: "hello world" }
 7
 8    const stubs = [
 9        {
10            predicates: [ {
11                equals: {
12                    method: "GET",
13                    "path": "/"
14                }
15            }],
16            responses: [
17                {
18                    is: {
19                        statusCode: 200,
20                        headers: {
21                            "Content-Type": "application/json"
22                        },
23                        body: JSON.stringify(response)
24                    }
25                }
26            ]
27        }
28    ];
29
30    const imposter = {
31        port: settings.hello_service_port,
32        protocol: 'http',
33        stubs: stubs
34    };
35
36    return mbHelper.postImposter(imposter);
37}
38
39module.exports = { addService };

这个代码定义了一个用一个单一的子来定义一个骗子,其中包含一个预言和一个响应,然后它将该对象发送到Mountebank服务器上,这个代码将添加一个新的模仿服务,该服务会听取GET请求,并在收到一个时返回url

让我们来看看前代代码创建的addService()函数,首先,它定义了响应消息hello world:

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

然后,它定义了一个 stub:

 1...
 2        const stubs = [
 3        {
 4            predicates: [ {
 5                equals: {
 6                    method: "GET",
 7                    "path": "/"
 8                }
 9            }],
10            responses: [
11                {
12                    is: {
13                        statusCode: 200,
14                        headers: {
15                            "Content-Type": "application/json"
16                        },
17                        body: JSON.stringify(response)
18                    }
19                }
20            ]
21        }
22    ];
23...

这个支柱有两个部分. 支柱部分正在寻找对根(/) URL 的GET请求. 这意味着支柱会在某人发送一个GET请求到模仿服务的根 URL 时返回答案.支柱的第二部分是响应数组. 在这种情况下,有一个答案,该答案返回了一个 JSON 结果,具有 HTTP 状态代码为 200。

最后的步骤定义了包含该支柱的欺骗者:

1...
2    const imposter = {
3        port: settings.hello_service_port,
4        protocol: 'http',
5        stubs: stubs
6    };
7...

这是你要发送到/imposters终端点的对象,以创建一个欺骗者以单个终端点嘲笑服务。前面的代码通过在设置文件中确定的端口设置port来定义你的欺骗者,将protocol设置为HTTP,并将stubs分配为欺骗者的 stubs。

现在你有一个伪装服务,代码将其发送到Mountebank服务器:

1...
2    return mbHelper.postImposter(imposter);
3...

如前所述,Mountebank 使用 REST API 来管理其对象,前面的代码使用您之前定义的postImposter()函数,向服务器发送POST请求以激活该服务。

一旦你完成了hello-service.js,保存并退出文件。

接下来,在index.js中调用新创建的addService()函数,在文本编辑器中打开文件:

1nano index.js

若要确保在创建 Mountebank 实例时调用该函数,请添加以下突出的行:

 1[label app/src/index.js]
 2const mb = require('mountebank');
 3const settings = require('./settings');
 4const helloService = require('./hello-service');
 5
 6const mbServerInstance = mb.create({
 7        port: settings.port,
 8        pidfile: '../mb.pid',
 9        logfile: '../mb.log',
10        protofile: '../protofile.json',
11        ipWhitelist: ['*']
12    });
13
14mbServerInstance.then(function() {
15    helloService.addService();
16});

当一个 Mountebank 实例被创建时,它会返回 promise. 一个承诺是直到稍后才确定其值的对象. 这可以用来简化非同步函数调用。

保存和退出index.js

要测试 Mountebank 初始化时是否会创建模仿服务,请启动应用程序:

1npm start

Node.js 进程将占用终端,所以打开一个新的终端窗口,并发送一个GET请求到http://localhost:5001/:

1curl http://localhost:5001

您将收到以下响应,表示服务正在工作:

1[secondary_label Output]
2{"message": "hello world"}

现在你已经测试了你的应用程序,回到第一个终端窗口,并使用CTRL +C离开Node.js应用程序。

在这个步骤中,你创建了你的第一个模仿服务,这是一个测试服务模仿,在响应GET请求时返回你好世界。这个模仿是用于演示目的;它真的不会给你通过构建一个小型Express应用程序而得到的任何东西。

步骤 6 – 构建一个数据支持的模具服务

虽然您在上一步创建的服务类型适用于某些场景,但大多数测试需要更复杂的响应集,在此步骤中,您将创建一个从 URL 获取参数并使用该参数来搜索 CSV 文件中的记录的服务。

首先,回到主app目录:

1cd ~/app

创建一个名为数据的文件夹:

1mkdir data

打开一个名为customers.csv的客户数据文件:

1nano data/customers.csv

添加以下测试数据,以便您的模仿服务有东西可获取:

1[label app/data/customers.csv]
2id,first_name,last_name,email,favorite_color 
31,Erda,Birkin,[email protected],Aquamarine
42,Cherey,Endacott,[email protected],Fuscia
53,Shalom,Westoff,[email protected],Red
64,Jo,Goulborne,[email protected],Red

这是由API模仿工具 Mockaroo生成的虚假客户数据,类似于您将虚假数据加载到服务本身的客户表中。

保存和退出文件。

然后在src目录中创建一个名为customer-service.js的新模块:

1nano src/customer-service.js

若要创建一个在 /customers/ 端点上听取 `GET’ 请求的骗子,请添加以下代码:

 1[label app/src/customer-service.js]
 2const mbHelper = require('./mountebank-helper');
 3const settings = require('./settings');
 4
 5function addService() {
 6    const stubs = [
 7        {
 8            predicates: [{
 9                and: [
10                    { equals: { method: "GET" } },
11                    { startsWith: { "path": "/customers/" } }
12                ]
13            }],
14            responses: [
15                {
16                    is: {
17                        statusCode: 200,
18                        headers: {
19                            "Content-Type": "application/json"
20                        },
21                        body: '{ "firstName": "${row}[first_name]", "lastName": "${row}[last_name]", "favColor": "${row}[favorite_color]" }'
22                    },
23                    _behaviors: {
24                        lookup: [
25                            {
26                                "key": {
27                                  "from": "path",
28                                  "using": { "method": "regex", "selector": "/customers/(.*)$" },
29                                  "index": 1
30                                },
31                                "fromDataSource": {
32                                  "csv": {
33                                    "path": "data/customers.csv",
34                                    "keyColumn": "id"
35                                  }
36                                },
37                                "into": "${row}"
38                              }
39                        ]
40                    }
41                }
42            ]
43        }
44    ];
45
46    const imposter = {
47        port: settings.customer_service_port,
48        protocol: 'http',
49        stubs: stubs
50    };
51
52    return mbHelper.postImposter(imposter);
53}
54
55module.exports = { addService };

此代码定义了一个服务模特,该模特寻找GET请求的URL格式为customers/<id>。当收到请求时,它会查询客户的id的URL,然后从CSV文件中返回相应的记录。

这个代码使用的 Mountebank 功能比你在上一个步骤中创建的你好服务多一些。 首先,它使用了 Mountebank 的功能,名为 behaviors

 1...
 2  _behaviors: {
 3      lookup: [
 4          {
 5              "key": {
 6                "from": "path",
 7                "using": { "method": "regex", "selector": "/customers/(.*)$" },
 8                "index": 1
 9              },
10              "fromDataSource": {
11                "csv": {
12                  "path": "data/customers.csv",
13                  "keyColumn": "id"
14                }
15              },
16              "into": "${row}"
17            }
18      ]
19  }
20...

钥匙属性使用一个常规表达式来解析输入路径,在这种情况下,您在URL中使用客户之后的id

fromDataSource属性指向您用于存储测试数据的文件。

进入属性将结果注入到变量${row}中,该变量在下面的部分中引用:

1...
2  is: {
3      statusCode: 200,
4      headers: {
5          "Content-Type": "application/json"
6      },
7      body: '{ "firstName": "${row}[first_name]", "lastName": "${row}[last_name]", "favColor": "${row}[favorite_color]" }'
8  },
9...

行变量用于填充响应的体,在这种情况下,它是与客户数据的 JSON 字符串。

保存和退出文件。

接下来,打开index.js,将新的服务模特添加到您的初始化函数:

1nano src/index.js

添加突出的线条:

 1[label app/src/index.js]
 2const mb = require('mountebank');
 3const settings = require('./settings');
 4const helloService = require('./hello-service');
 5const customerService = require('./customer-service');
 6
 7const mbServerInstance = mb.create({
 8        port: settings.port,
 9        pidfile: '../mb.pid',
10        logfile: '../mb.log',
11        protofile: '../protofile.json',
12        ipWhitelist: ['*']
13    });
14
15mbServerInstance.then(function() {
16    helloService.addService();
17    customerService.addService();
18});

保存和退出文件。

现在开始Mountebank使用npm start。这将隐藏提示,所以打开另一个终端窗口。通过发送GET请求到localhost:5002/customers/3来测试您的服务。

1curl localhost:5002/customers/3

你会看到以下答案:

1[secondary_label Output]
2{
3    "firstName": "Shalom",
4    "lastName": "Westoff",
5    "favColor": "Red"
6}

在此步骤中,您创建了一个模仿服务,该服务会从 CSV 文件中读取数据,并将其作为 JSON 响应返回,从这里开始,您可以继续构建更复杂的模仿服务,匹配您需要测试的服务。

结论

在本文中,您使用Mountebank和Node.js创建了自己的服务欺骗应用程序,现在您可以构建欺骗服务,并与您的团队共享它们,无论是涉及到您需要测试的供应商服务的复杂情况,还是在等待其他团队完成工作时简单的欺骗,您可以通过创建欺骗服务来保持团队的移动。

如果你想了解更多关于Mountebank的信息,请查看他们的文件(http://www.mbtest.org/)。如果你想容器化这个应用程序,请参阅 Containerizing a Node.js Application for Development With Docker Compose.如果你想在类似生产的环境中运行这个应用程序,请参阅 How To Set Up a Node.js Application for Production on Ubuntu 18.04

Published At
Categories with 技术
comments powered by Disqus