Module mocks - мощный инструмент для написания модульных тестов с Jest. Они позволяют изолировать тестируемый код от его зависимостей, что приводит к целенаправленным и менее хрупким тестам. Но, как и многие другие мощные инструменты, имитация модулей временами может быть сложной.
В этом посте мы узнаем, как имитировать разные значения для одного и того же модуля в разных тестах.
Допустим, у вас есть модуль greetings
, экспортирующий hello
функцию, которая зависит от другого модуля, чтобы знать текущий язык приложения. Что-то вроде этого:
Издевайтесь надо мной однажды, позор вам. Издеваешься надо мной дважды ... снова стыдно?
Написание модульного теста для hello
включает в себя имитацию lang
зависимости для управления текущим языком:
Вы можете использовать jest.mock
(строка 4) для имитации зависимости lang
. В приведенном выше примере макет модуля имеет поле current
, в котором задана фиктивная функция. Вы хотите протестировать обе ветви hello,
, поэтому вы используете mockReturnValueOnce
, чтобы фиктивная функция возвращала "GL"
в первом вызове и"EN"
во втором.
Вы запускаете jest
, оба теста проходят, миссия выполнена. Вы счастливый разработчик.
Но ждать. Попытайтесь сфокусировать второй тест, используя it.only
. Ой, рыба! Теперь тест не проходит:
Expected value to equal: "Hi!" Received: "Ola!"
Что ж, похоже, что установка фиктивного модуля слишком хрупкая: вы ожидаете, что фиктивная функция будет вызываться в том же порядке, в котором вы ее определяете. Это связывает ваш порядок выполнения теста с макетом настройки, и это ... ну, не очень хорошо :)
Держите своих друзей близко, а ваши насмешки ближе
Есть лучший способ настроить такой тест:
Ключевое различие заключается в строках 3, 13 и 20. Вы импортируете фиктивный модуль (строка 3), чтобы получить доступ к фиктивной функции. Затем вы вызываете mockImplementation
(строки 13 и 20) внутри тестового тела, чтобы установить правильное возвращаемое значение. Теперь вы можете использовать it.only
, когда захотите!
Насмешливые значения, а не функции
Предположим, greetings
изменяется: теперь он должен использовать другой модуль для получения текущего значения языка. Новый модуль называется appEnv
и экспортирует текущий язык как значение. Теперь greetings
выглядит так:
Вы пытаетесь соответствующим образом изменить тест:
Вы снова запускаете jest
и ... терпит неудачу! Вы получаете сообщение об ошибке:
greetings.test.js: "currentLanguage" is read-only
Проблема в том, что вы не можете присвоить значение тому, что импортировали. В предыдущих примерах вы импортировали фиктивную функцию current
и использовали mockImplementation
для изменения ее возвращаемого значения, но импортированное значение осталось прежним¹. Теперь ты не можешь этого сделать.
Не импортировать, требовать
Что вам нужно, так это способ использовать разные макеты для каждого теста. Если вы попробуете что-то вроде этого, вы все равно увидите неудачный тест:
В предыдущем фрагменте кода hello
импортируется до того, как будет имитироваться его зависимость, поэтому тесты выполняются с использованием фактической реализации appEnv
. Пришло время отказаться от всей этой причудливой ерунды ES6. Просто используйте старый добрый require
, когда закончите настройку макета модуля:
Проведите тесты сейчас ... Все еще красный, верно? Что ж, вам нужно сказать Jest, чтобы он очищал реестр модулей перед каждым тестом, чтобы каждый раз, когда вы вызываете require
, вы получаете новую версию необходимого модуля.
Это последний рабочий код:
Подводя итоги
Мы видели, как имитировать модуль для экспорта разных значений для разных тестов. Когда экспорт является функцией, вы можете смоделировать его с помощью jest.fn()
и изменить его реализацию для каждого теста. Когда экспорт является значением, вам нужно вернуться к основам и использовать require
(и jest.resetModules
), чтобы порядок выполнения не мешал вашей фиктивной настройке².
¹ Ну, технически привязка (а не значение) остается неизменной.
² Вы можете взглянуть на jest.doMock
, если хотите изменить фиктивное значение между двумя разными утверждениями одного и того же теста.