Мокирование сервисов с помощью Mountebank и Node.js

В сложных сервис-ориентированных архитектурах (SOA) для выполнения определенного рабочего процесса программам часто нужно вызывать несколько сервисов. Это не проблема, когда всё уже готово. Но если для кода, над которым вы работаете, требуется определенный сервис, а он все еще находится в разработке, вы можете застрять на этом этапе в ожидании, пока другие команды закончат свою работу. Кроме того, для тестирования вам может потребоваться взаимодействие с сервисами сторонних провайдеров, такими как API погоды или система учета. Провайдеры обычно не предоставляют столько сред, сколько нужно, и зачастую не позволяют легко контролировать данные тестов в своих системах. В этих ситуациях незавершенные сервисы и сервисы, находящиеся вне вашего контроля, могут затруднить тестирование кода.

Решением всех этих проблем является создание мока сервиса. Мок – это код, имитирующий тот сервис, который вы будете использовать в конечном продукте. Но он легче и проще (в том числе в управлении), чем реальный сервис, который вы использовали бы в производстве. Вы можете настроить свой фиктивный сервис так, чтобы он возвращал ответ по умолчанию или определенные тестовые данные, а затем запускать интересующее вас программное обеспечение, как если бы сервис действительно работал. Гибкое средство мокирования сервисов может сделать ваш рабочий процесс быстрее и эффективнее.

В условиях предприятий создание фиктивных сервисов иногда называют виртуализацией сервисов. Виртуализация сервисов часто ассоциируется с дорогими корпоративными инструментами, но для мокирования сервисов вам не нужен дорогой инструмент. Mountebank – это свободный и открытый инструмент для разработки моков сервисов, который вы можете использовать для мокирования HTTP-сервисов, включая сервисы REST и SOAP . Вы также можете использовать его для проверки SMTP или TCP-запросов.

В этом мануале мы создадим два гибких приложения для обслуживания клиентов, используя Node.js и Mountebank. Оба мок-сервиса будут прослушивать запросы REST в HTTP по определенным портам. В дополнение к этому простому функционалу, сервис также будет извлекать мок-данные из файла CSV. В результате вы сможете мокировать все виды поведения сервиса, благодаря чему разрабатывать и тестировать приложения станет легче.

Требования

1: Запуск приложения Node.js

На этом этапе мы создадим базовое приложение Node.js, оно будет служить основой для Mountebank и мок-сервисов, которые мы создадим на последующих этапах.

Примечание: 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 сценарии определяют команды, которые создают, запускают и тестируют ваше приложение. Вы можете использовать здесь значения по умолчанию или ввести название пакета, номер версии и т. д.

После выполнения этой команды у вас будет простое приложение Node.js, включая файл package.json.

Теперь установите пакет Mountebank, используя команду:

npm install -save mountebank

Эта команда загружает пакет Mountebank и устанавливает его в приложение. Обязательно используйте флаг -save, чтобы обновить файл package.json и добавить в него Mountebank в качестве зависимости.

Затем добавьте в package.json стартовый скрипт, который запускает команду node src/index.js. Этот скрипт определяет точку входа приложения как index.js, ее мы создадим на следующем этапе.

Откройте файл package.json в текстовом редакторе. Вы можете использовать любой текстовый редактор, но в этом мануале это будет nano.

nano package.json

Перейдите в раздел «scripts»  и добавьте строку «start»: «node src/index.js». Это добавит команду start для запуска вашего приложения.

Теперь файл package.json должен выглядеть примерно так (учитывая то, как вы ответили на запросы команды npm init):

{
"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. Далее мы добавим файл для хранения настроек приложения.

2: Создание файла settings

На этом этапе вы создадите файл settings, который определяет, какие порты будет прослушивать экземпляр Mountebank и два мок-сервиса.

Каждый раз, когда вы запускаете экземпляр Mountebank или мок-сервис, вам нужно указать, по какому порту будет работать этот сервис (например, http://localhost:5000/). Поместив их в файл settings, вы позволите другим частям приложения импортировать эти настройки всякий раз, когда им нужно знать номер порта для сервисов и экземпляра Mountebank. Вы можете закодировать порты в свое приложение как константы, но если вы сохраните их в файле, позже будет проще изменить настройки. То есть вам нужно будет изменить значения только в одном месте.

Начните с создания каталога src в каталоге вашего приложения:

mkdir src

Перейдите в каталог, который вы только что создали:

cd src

Создайте файл settings.js и откройте его в текстовом редакторе:

nano settings.js

Затем добавьте настройки для портов основного экземпляра Mountebank и двух мок сервисов, которые мы создадим позже:

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

Этот файл содержит три записи:

  • port: 5000 присваивает порт 5000 основному экземпляру Mountebank
  • hello_service_port: 5001 назначает порт 5001 для тестового сервиса Hello World, который вы создадите чуть позже
  • customer_service_port: 5002 присваивает порт 5002 мок-сервису (это приложение выдаст данные CSV).

Если эти порты у вас уже заняты, замените их свободными. module.exports = позволяет другим файлам импортировать эти настройки.

Вы создали файл settings.js и указали в нем порты, которые Mountebank и мок-сервисы будут прослушивать, а также сделали эти настройки доступными для других частей вашего приложения.

3: Создание сценария инициализации

На этом этапе мы создадим файл, который запускает экземпляр Mountebank. Этот файл будет точкой входа приложения, то есть при запуске приложения этот сценарий будет запускаться первым. Позже мы добавим в этот файл дополнительные строки при создании новых мок-сервисов.

В каталоге src создайте файл index.js и откройте его в редакторе:

nano index.js

Чтобы запустить экземпляр Mountebank, который будет работать по порту, указанному в файле settings.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: ['*']
});

Этот код делает три вещи. Во-первых, он импортирует ранее установленный npm-пакет Mountebank (const mb = require (‘mountebank’);). Затем он импортирует модуль settings, созданный на предыдущем этапе (const settings = require(‘./settings’);). После этого он создает экземпляр сервера Mountebank с помощью mb.create().

Сервер будет прослушивать порт, указанный в файле settings. Параметры pidfile, logfile и protofile предназначены для внутренних файлов Mountebank (файл для записи ID процесса, лог и файл для загрузки пользовательских протоколов). Параметр ipWhitelist указывает, какие IP-адреса могут взаимодействовать с сервером Mountebank. В этом случае мы открываем его для всех 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/

Вы получите такой ответ:

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

JSON, который возвращает Mountebank, описывает три различные конечные точки, которые вы можете использовать для добавления или удаления объектов в Mountebank. Используя curl для отправки запросов на эти конечные точки, вы можете взаимодействовать с экземпляром Mountebank.

Когда вы закончите, вернитесь к своему первому окну и выйдите из приложения, используя Ctrl + C. Это закроет приложение Node.js, чтобы вы могли продолжить его разработку.

Теперь у вас есть приложение, которое запускает экземпляр Mountebank. На следующем этапе мы создадим клиент Mountebank, который использует REST-запросы для добавления мок-сервисов в приложение Mountebank.

4: Создание клиента Mountebank

Mountebank взаимодействует с помощью REST API. Вы можете управлять ресурсами своего экземпляра Mountebank, отправляя HTTP-запросы различным конечным точкам. Чтобы добавить мок-сервис, нужно отправить POST HTTP-запрос конечной точке imposters. Imposter – так называются мок-сервисы в Mountebank. Imposter-ы могут быть простыми или сложными, в зависимости от поведения, которое вы хотите реализовать в своем мок-сервисе.

Сейчас мы создадим клиент Mountebank для автоматической отправки POST-запросов в Mountebank. Вы можете отправить POST-запрос конечной точке imposters, используя curl или Postman, но вам придется отправлять этот же запрос каждый раз при перезапуске сервера тестирования. Если вы используете образец API с несколькими моками, будет эффективнее написать клиентский скрипт, который сделает это за вас.

Начните с установки библиотеки node-fetch:

npm install -save node-fetch

Библиотека node-fetch предоставляет реализацию JavaScript Fetch API, которую вы можете использовать для написания более коротких HTTP-запросов. Вы можете использовать стандартную http-библиотеку, но node-fetch – это более легкое решение.

Теперь создайте клиентский модуль для отправки запросов в Mountebank. Вам нужно только разместить imposter-ы, поэтому этот модуль будет иметь один метод.

Используйте nano для создания файла mountebank-helper.js:

nano 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 и файла settings. Затем этот модуль предоставляет функцию postImposter, которая отправляет мок сервисы в Mountebank. Далее body: определяет, что функция принимает JSON.stringify (body), объект JavaScript. Этот объект – то, что мы собираемся отправить в сервис Mountebank. Поскольку этот метод работает локально, мы выполняем свой запрос к 127.0.0.1 (localhost). Метод fetch берет объект, отправленный в параметрах, и отправляет запрос POST на url.

5: Создание первого мок-сервиса

На предыдущих этапах вы создали приложение, которое запускает сервер Mountebank, и код для вызова этого сервера. Теперь пришло время использовать этот код для создания imposter-а, или фиктивного сервиса.

В Mountebank каждый imposter содержит стабы. Стабы (stubs) — это наборы конфигурации, которые определяют будущий ответ imposter-а. Стабы можно разделить на комбинации предикатов и ответов. Предикат – это правило, которое запускает ответ imposter-а. Предикаты могут использовать множество различных типов информации, включая URL-адреса, содержимое запроса (XML или JSON) и методы HTTP.

С точки зрения приложения «модель-вид-контроллер» (MVC) imposter действует как контроллер, а стабы – как действия внутри этого контроллера. Предикаты – это по сути правила маршрутизации, которые указывают на конкретное действие контроллера.

Чтобы создать свой первый мок-сервис, откройте файл hello-service.js. Этот файл будет содержать определение сервиса.

Откройте hello-service.js в текстовом редакторе:

nano 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 };

Этот код определяет imposter с одним стабом, который содержит предикат и ответ. Затем он отправляет этот объект на сервер 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)
}
}
]
}
];
...

Стаб состоит из двух частей. Предикат ищет GET-запрос к корневому URL (/). Это означает, что стабы будут возвращать ответ, когда кто-то отправляет GET-запрос на корневой URL мок-сервиса. Вторая часть стаба – массив ответов. В этом случае есть один ответ, который возвращает результат JSON с кодом состояния HTTP 200.

Последний фрагмент определяет imposter, который содержит этот стаб:

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

Это объект, который мы собираемся отправить в конечную точку /imposters. Он создаст imposter-а, который будет имитировать сервис с одной конечной точкой. Вышеописанный код определяет imposter-а так:

  • в port он устанавливает порт, который вы указали в файле settings
  • в protocol он устанавливает HTTP
  • определяет stubs как стабы imposter-а.

Теперь, когда у вас есть мок-сервис, код отправляет его на сервер Mountebank:

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

Как упоминалось ранее, Mountebank использует REST API для управления своими объектами. В предыдущем коде используется функция postImposter(), которую мы ранее определили для отправки POST-запроса для активации сервиса.

Когда вы закончите работу с hello-service.js, сохраните файл и выйдите из него.

Затем вызовите только что созданную функцию addService() в index.js. Откройте файл в текстовом редакторе:

nano index.js

Чтобы функция вызывалась при создании экземпляра Mountebank, добавьте следующие выделенные строки:

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 создается, он возвращает промис. Промис – это объект, чье значение не определено до последнего момента. Промис может использоваться для упрощения асинхронных вызовов функций. В предыдущем коде функция .then(function () {…}) выполняется при инициализации сервера Mountebank, что происходит при разрешении промиса.

Сохраните и закройте index.js.

Чтобы убедиться, что мок-сервис создается при инициализации Mountebank, запустите приложение:

npm start

Процесс Node.js будет занимать терминал, поэтому откройте новое окно и отправьте GET-запрос на http://localhost:5001/:

curl http://localhost:5001

Вы получите следующий ответ, который значит, что сервис работает:

{"message": "hello world"}

Теперь, когда вы проверили свое приложение, вернитесь в первое окно терминала и остановите приложение Node.js, используя Ctrl + C.

Итак, вы создали свой первый мок-сервис. Это тестовый фиктивный сервис, который возвращает hello world в ответ на GET-запрос. Этот мок предназначен для демонстрации; на самом деле он не дает вам ничего, чего вы не могли бы получить, создав небольшое приложение Express. На следующем этапе мы создадим более сложный мок-сервис, который использует некоторые функции Mountebank.

6: Создание мок-сервиса на основе данных

Хотя простой мок-сервис, который вы создали только что, отлично подходит для некоторых сценариев, в большинстве тестов требуется более сложный набор ответов. На этом этапе мы создадим сервис, который берет параметр из URL и использует его для поиска записи в файле CSV.

Сначала вернитесь в основной каталог приложения:

cd ~/app

Создайте каталог data:

mkdir data

Откройте файл для данных о клиентах по имени customers.csv:

nano data/customers.csv

Добавьте следующие тестовые данные, чтобы мок-сервису было что извлекать:

id,first_name,last_name,email,favorite_color
1,Erda,Birkin,ebirkinb@google.com.hk,Aquamarine
2,Cherey,Endacott,cendacottc@freewebs.com,Fuscia
3,Shalom,Westoff,swestoffd@about.me,Red
4,Jo,Goulborne,jgoulbornee@example.com,Red

Это условные данные о клиентах, сгенерированные инструментом Mockaroo.

Сохраните и закройте файл.

Затем создайте новый модуль под названием customer-service.js в каталоге src:

nano src/customer-service.js

Чтобы создать imposter-а, который прослушивает GET-запросы по конечной точке /customers/, добавьте следующий код:

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 };

Этот код определяет мок, который ищет GET-запросы с форматом customers/<id>. Когда запрос получен, мок-сервис запросит id  клиента, а затем вернет соответствующую запись из файла CSV.

Этот код использует несколько дополнительных возможностей Mountebank. Во-первых, он использует так называемую функцию behaviors, поведения. Поведение – это способ добавить функциональность в стаб. В этом случае используется поведение 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 использует регулярное выражение для анализа входящего пути. В этом случае берется id, который идет в URL после customers/.

Свойство 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]" }'
},
...

Переменная row используется для заполнения тела ответа. В нашем случае это строка JSON с данными клиента.

Сохраните и выйдите из файла.

Затем откройте index.js, чтобы добавить новый мок-сервис в функцию инициализации:

nano 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();
});

Сохраните и закройте файл.

Теперь запустите Mountebank с помощью команды npm start. Для этого откройте другое окно терминала. Протестируйте свой сервис, отправив GET-запрос на localhost:5002/customers/3. Этот запрос будет искать информацию о клиенте под идентификатором 3.

curl localhost:5002/customers/3

Вы должны увидеть такой ответ:

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

Вы создали мок-сервис, который считывает данные из CSV-файла и возвращает их в виде ответа JSON. Теперь вы можете продолжать создавать более сложные моки, которые заменят вам сервисы, необходимые для тестирования.

Заключение

В этом мануале вы создали собственное приложение с мок-сервисами, используя Mountebank и Node.js. Теперь вы можете создавать мок-сервисы и делиться ими со своей командой. Теперь вам не нужно ждать, пока другая команда закончит свою работу, вы и ваша команда можете работать с кодом, используя мок-сервисы.

Если вы хотите узнать больше о Mountebank, ознакомьтесь с документацией. Если вы хотите создать контейнер для этого приложения, ознакомьтесь с мануалом Контейнеризация приложения Node.js для разработки. Если вы хотите запустить это приложение в производственной среде, читайте Подготовка приложения Node.js к производству в Ubuntu 18.04.

Tags: , , ,