作者选择了 开放式互联网 / 自由言论基金作为 写给捐赠计划的一部分接受捐款。
介绍
在复杂的 服务导向架构 (SOA)中,程序通常需要调用多个服务来运行一个特定的工作流程. 一旦一切都做好了,但如果您正在开发的代码需要一个仍在开发的服务,您可能会被困在等待其他团队在开始工作之前完成工作。此外,为测试目的,您可能需要与外部供应商服务(如天气API或记录系统)进行交互。
所有这些问题的解决方案是创建一个 service mock. 服务 mock 是模拟你在最终产品中使用的服务的代码,但它比你在生产中使用的实际服务更轻,更不复杂,更容易控制。
在企业设置中,制作模仿服务有时被称为 service virtualization. 服务虚拟化通常与昂贵的企业工具相关联,但您不需要昂贵的工具来模仿服务。
在本指南中,您将使用 Node.js和Mountebank构建两种灵活的服务模仿应用程序。这两个模仿服务将倾听 HTTP 中的 REST 请求的特定端口。除了这种简单的模仿行为外,该服务还将从 comma-separated values (CSV) 文件中获取模仿数据。经过本教程,您将能够模仿各种服务行为,以便您更容易地开发和测试您的应用程序。
前提条件
要遵循本教程,您将需要以下内容:
- Node.js 版本 8.10.0 或更高版本安装在您的计算机上。本教程将使用 8.10.0 版本。 要安装 Node.js,请参阅 如何在 Ubuntu 18.04 上安装 Node.js或 如何在 macOS 上安装 Node.js 并创建本地开发环境。
- 一个工具来执行 HTTP 请求,如 cURL或 Postman.本教程将使用 cURL,因为它在大多数机器上默认安装;如果您的计算机没有 cURL,请参阅 安装文件.
步骤 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');
)。
服务器会听取设置文件中指定的端口。pidfile
、logfile
和protofile
参数是用于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
请求. 您可以使用curl
或Postman
发送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。欺骗者是定义欺骗者会给出的答案的配置集。欺骗者可以进一步分为 predicates 和 responses的组合。先例是触发欺骗者响应的规则。欺骗者可以使用许多不同类型的信息,包括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。