Muon Mockify
Простой способ замокать ваш проект.
Описание
Данный модуль призван упростить процесс интеграционного и юнит-тестирования Вашего проекта. Модуль исходно разработан как часть среды интеграционного и юнит-тестирования веб-фреймворка Muon.js, однако не зависит от него, поэтому Вы можете использовать muon-mockify для тестирования Ваших персональных проектов.
Модуль является надстройкой над методом require
модуля Module
, входящего в состав Node.js, и позволяет замещать экспортируемые модули на их mock-аналоги.
Установка
Для включения модуля в Ваш NPM-проект следует выполнить:
$ npm install --save-dev muon-mockify
Если Вы намерены использовать последню версию из GIT:
$ git clone https://gitlab.muonapps.com/muon-group/muon-mockify$ npm install $ npm test # xUnit-отчет о выполнении тестов будет помещен в файл /reports/xunitxml$ npm run-script systemtest # xUnit-отчет о выполнении системных тестов будет помещен в файл /reports/xunit-systemxml $ npm link # создаст глобальную ссылку на NPM модуль в системе могут потребоваться права администратора # далее в Вашем проекте$ npm link muon-mockify
Спецификация и интерфейс
Mockify выполняет подмену базового загрузчика модулей require
, устанавливая поверх него специальную функцию-враппер, которая выполняет поиск mock-модулей в директории ./mock_modules
в корне проекта (либо в директориях определеннных методами setMockifyDir
и addMockifyDir
, далее везде MOCKIFY_DIR), и в случае успеха, замещает экспорт модуля соответствующим mock-объектом, либо объектом, переданным в mockify напряму через метод mockify.enableMock
.
Содержимое MockifyDir. Правила соответствия имен.
Далее будет использоваться следующая структура проекта для примеров:
$ tree . |- lib/ |- |--- mymodule.js |- main.js |- mock_modules/ |- |--- lib/ |- |--- |--- mymodule.js |- |--- node_modules/ |- |--- |--- foo.js |- |--- |--- foo/ |- |--- |--- |--- optional.js |- |--- |--- http.js |- node_modules/ |- |--- foo/ |- |--- |--- lib/ |- |--- |--- |--- foo.js |- |--- |--- main.js # require("foo") |- |--- |--- optional.js # require("foo/optional") |- |--- |--- package.json # main запись ссылается на main.js |- |--- muon-mockify/ |- |--- |--- main.js |- package.json |- test/ |- |--- test.js
В процессе поиска mock-объектов - кандидатов на замещения ориганиальных модулей, экспортируемых с помощью require
, поиск будет осуществляться следующим образом:
- для локальных модулей проекта путь будет формироваться из значения: MOCKIFY_DIR+<относительный путь к модулю> по отношению к корневой директории проекта. При этом не имеет значения, какой относительный путь для экспорта модуля был передан в метод
require
. - для модулей-зависимостей (NPM-пакетов), а также нативных модулей Node.js путь будет определяться исходя из следующего значения: MOCKIFY_DIR+"/node_modules"+<имя модуля>.
- для альтернативных модулей NPM-пакетов, доступных через слэш, например,
require("foo/optional")
применяется правило формирования пути: MOCKIFY_DIR+"/node_modules"+<имя модуля>+"/"+<путь к алтернативному модулю> - Для внутренних локальных модулей сторонних NPM-пакетов правила поиска не применяются, всегда возвращается оригинальный модуль.
ВАЖНО: При использовании muon-mockify
, в частности при управлении директориями MOCKIFY_DIR, а также при определении, относится ли запрашиваемый модуль к проекту, или находится за его пределами и не должен быть замокан, используется текущая работчая директория процесса (Current Workin Directory: process.cwd()
).
Если вы запускаете тесты не из корневой директории Вашего проекта, либо сменили CWD в процессе их выполнения , muon-mockify
может работать некорректно.
Ниже представлена таблица сооветствия аргументов переданных в require
и результирующих путей, по которым будет производиться поиск mock-объекта:
имя файла, из которого выполняется require |
аргумент require |
результирующий путь |
---|---|---|
./main.js | ./lib/mymodule | ./mock_modules/lib/mymodule |
./test/test.js | ../lib/mymodule | ./mock_modules/lib/mymodule |
./main.js | foo | ./mock_modules/node_modules/foo |
./main.js | foo/optional | ./mock_modules/node_modules/foo/optional |
./main.js | http | ./mock_modules/node_modules/http |
./node_modules/foo/optional.js | ./lib/foo | ./node_modules/foo/lib/foo |
Также следует учитывать, что в целях поддержки JavaScript-производных языков програмирования (например, CoffeeScript) расширения файлов в процессе поиска не учитываются. При этом, если в искомой директории будет обнаружен mock-модуль подходящий по имени, но с неизвестным расширением, будет выполнена попытка его экспорта, что в конечном счете приведет к ошибке.
Интерфейс:
- mockfiy.enable( id | [ id ] )
Активирует функцию-враппер для метода require
для модуля (модулей) с именем id
, либо по-умолчанию для всех подключаемых модулей, если id
не задан. После вызова данного метода, любой вызов require
будет предварительно выполнять поиск соответствующих mock-модулей в директориях MOCKIFY_DIR. Если модуль существует, то вместо запрашиваемого модуля будет экспортирован найденный mock-объект. Если MOCKIFY_DIR включает в себя несколько директорий, и при этом более чем одна из директорий содержит запрашиваемый mock-модуль, require
вернет первый найденный объект в соответсвии с порядком объявления директорий. Если же ни в одной из директорий MOCKIFY_DIR mock-объект не был найден, метод вернет исходный запрашиваемый модуль, либо выбросит исключения, если последний также отстутствует.
# /mainjs:var mockify = ;mockify;var mymodule = ;var mymodule_alt = ;var mymodule_orig = mockify;var mymodule_mock = mockify;console; // FALSEconsole; // TRUEconsole; // TRUE var foo = ;var foo_orig = mockify;console; // FALSE var foo_opt = ;var foo_opt_orig = mockify;console; // FALSE
В качестве опционального параметра в метод может быть передан путь к локальному модулю или имя внешнего модуля (либо список подобных путей и имен), для которых необходимо загружать mock-объкты. В этом случае функция враппер будет срабатывать только для указаных имен.
Важно: Обратите внимание, что при передаче в метод enable
аргумента - идентификатора модуля, используется путь данного модуля относительно текущей директории процесса, а не модуля, в котором этот метод вызван.
Пример:
var mockify = ;mockify; // или mockify.enable(["./lib/mymodule"]);var mymodule = ;var mymodule_orig = mockify;console; // FALSE var foo = ;var foo_orig = mockify;console; // TRUE mockify;
Повторное выполнение mockify.enable
с аргументом добавит в список имен для поиска mock-объектов новые значения. При этом, если первый вызов был выполнен без аргумента, повторный вызов не будет иметь смысла и не приведет ни к каким изменениям.
- mockfiy.enableMock( id, mock )
Также как и метод mockify.enable
активирует враппер метода require
, однако, вместо поиска модуля в файловой системе в директориях MOCKIFY_DIR, метод require
вернет значение mock
, переданное в качестве аргумента. Данный метод может быть вызван вместе с методом mockify.enable
, при этом в процессе поиска mock-объекта приоритет будет за значением, переданным через mockify.enableMock
.
Пример:
var mockify = ; var httpMock = {} {} mockify;var http = ;console; // TRUE
Также как и в enable
значение аргумента id
при передаче в данный метод, определяет путь модуля относительного текущей директории процесса.
- mockfiy.removeMock( [ id ] )
Вызов данного метода отменяет действие вызова mockify.enableMock
для имен и/или путей переданных в качестве аргумента, а также добавляет соответствующие имена в игнорируемый список в процессе поиска mock-модулей в файловой системе в директориях
MOCKIFY_DIR. Также метод очищает require кэш для указанных модулей, таким образом повторный экспорт указанных модулей приведет к их повторной загрузке и исполнению. Данный метод может быть полезен, когда требуется исключить из полного списка существующих mock-объектов, подключаемых через mockify.enable
, один или несколько модулей.
Пример:
var mockify = ;mockify;mockify;var mymodule = ;var mymodule_orig = mockify;console; // TRUE
- mockfiy.disable()
Отключает враппер require
, а также очищает кэш загруженных модулей. Также отменяет все действия и фильтры, установленные методами mockify.enable
, mockify.enableMock
и mockify.removeMock
.
- mockify.isEnabled()
Возвращает состояние работы враппера.
- mockfiy.original( id )
Выполняет вызов оригинального метода require
, игнорируя все текущие параметры mockify. В случае, если враппер метод неактивен (методы mockify.enable
или mockify.enableMock
ниразу не использовались, либо был вызван mockify.disable
), то вызов mockify.original
выведет сооветствующее сообщение в stderr
.
- mockfiy.getMockifyDirs() : [ path ]
Возвращает список текущих путей поиска MOCKIFY_DIR mock-объектов.
- mockfiy.setMockifyDir( path | [path] )
Сбрасывает текущее значение и устанавливает новый путь либо список путей MOCKIFY_DIR. Порядок указания директорий опредяляет приоритет путей (по-убыванию) в процессе поиска mock-модулей. Если одна из устанавливаемых директорий отсутствует, соответствующее сообщение будет выведено в stderr
.
Пример:
var mockfiy = ;console;// ['/home/user/foo_project/mock_modules'] mockify;console;// ['/opt/nodejs/mocks','/home/user/local/mocks']
- mockfiy.addMockifyDir( path | [path] )
Добавляет новые пути в MOCKIFY_DIR для поиска mock-модулей. Порядок указания директорий опредяляет приоритет путей (по-убыванию) в процессе поиска mock-модулей. Если одна из устанавливаемых директорий отсутствует, соответствующее сообщение будет выведено в stderr
.
Пример:
var mockfiy = ;console;// ['/home/user/foo_project/mock_modules'] mockify;console;// ['/home/user/foo_project/mock_modules','/opt/nodejs/mocks','/home/user/local/mocks']
- mockfiy.resetMockifyDir()
Устанавливает в качестве единственного текущего пути MOCKIFY_DIR значение по умолчанию: ./mock_modules
в корне проекта.
Примеры тестов
Ниже приведен небольшой туториал - пример тестирования примитивного HTTP клиента, работающего поверх нативного Node.js модуля http
.
# /lib/myhttpclientjs var http = ; exports{ http;}
Очевидно, для тестирования подобного модуля требуется сетевая часть, которая выполнит HTTP-запрос. Чтобы не тратить время на организацию тестового веб-сервера, и также не зависить от внешних факторов, способных повлиять на успешность выполнения тестов (например, доступность сети, доступность запрашиваемого сервера с тестовыми данными, валидность получаемых данных и т.д.) нам потребуется создать mock-объект для модуля http
.
# /test/http-mockjs var Readable = Readable _ = util = ; /// Реализация поведения IncommingMessage модуля 'http'{ Readable; this__offset = 0; thisstatus = status; thisheaders = {}; this__data = data;}util;_; /// Настраиваемый mock-класс имитирующий поведение модуля 'http'./// httpMockStatus и httpMockRet - то что безусловно должен вернуть HTTP клиентmodule{ _;}
Теперь мы готовы написать сам тест совместно с muon-mockify
:
# /test/httpclientTestjs ;var expect = expect mockify = ; ;
В определенный момент Вам станет ясно, что реализация mock-модуля HttpMock (и любых других подобных модулей) стала достаточно универсальной, и Вы можете использовать ее также в остальных сетевых тестах. Тогда соответствующий модуль будет целесообразно поместить в MOCKIFY_DIR.
Теперь предположим, что у нас есть еще один модуль, который выполняет обработку данных, полученных с помощью нашего же HttpClient.
# /lib/dataprocjs var httpClient = ; exports { httpClient;} exports { ...}
Для тестирования данного модуля нам потребуется mock-реализация локального модуля HttpClient:
# /mock_modules/lib/myhttpclientjs var mockErrmockStatusmockData;exports{ mockStatus = status; mockData = data;} exports { ;}
В отличии от HttpMock модуля, мы создали настраиваемый вариант mock-объекта и разместили в директории MOCKIFY_DIR. По этому сам тест может быть немного упрощен:
# /test/dataprocTestjs ;var expect = expect mockify = ; ;
и так далее..
Что дальше...
В последствии при создании сьюит юнит-тестов Вы сможете определить глобальный setup
и teardown
методы, которые будут активировать враппер require
.
Доступ к тестрируемому модулю следует выполнять с помощью метода mockify.original
. Для mocha
тестов это будет выглядеть примерно следующим образом:
;
Помимо этого вы также можете создавать отдельные сьюиты с независимыми тестовыми сценариями. В сложном проекте это может быть удобно для тестирования отдельных значимых аспектов поведения программного продукта. Добиться этого можно, используя наборы mock-модулей с согласованным поведением и (или) набором тестовых данных, помещенных в отдельные переключаемые директории MOCKIFY_DIR.
Лицензия
Исходный код данного проекта распространяется под лицензией MIT.