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() и изменить его реализацию для каждого теста. Когда экспорт является значением, вам нужно вернуться к основам и использовать requirejest.resetModules), чтобы порядок выполнения не мешал вашей фиктивной настройке².

¹ Ну, технически привязка (а не значение) остается неизменной.

² Вы можете взглянуть на jest.doMock, если хотите изменить фиктивное значение между двумя разными утверждениями одного и того же теста.