Краткое руководство о том, как проверить, что функция зависит от другой функции, экспортируемой тем же модулем.

Эта проблема

У вас есть модуль, который экспортирует несколько функций. Одна из этих функций зависит от другой функции того же модуля.

export function foo () { ... }
export function bar () { foo() }

Вы хотите утверждать, что при выполнении bar() он также запускает выполнение foo().

Это может показаться классической ситуацией для использования функций Jest spyOn или mock. Следовательно, вы ожидаете, что сможете написать такой тест:

import * as myModule from './myModule';
test('calls myModule.foo', () => {
  const fooSpy = jest.spyOn(myModule, 'foo');
myModule.bar();
expect(fooSpy).toHaveBeenCalledTimes(1);
});

Удивительно или нет, но этот тест завершился неудачно с сообщением Expected mock function to have been called one time, but it was called zero times.:

Вы можете попробовать использовать jest.mock() или любой другой интерфейс Jest, чтобы утверждать, что ваш bar метод зависит от вашего foo метода.

В конечном итоге вы обвините Jest в том, что он вызвал ошибку, и пожалеете о том, что решили начать писать свои тесты с его помощью.

По правде говоря, это не про шутку. Речь идет о самом JavaScript.

Причина

Более подробно, это из-за того, как Javascript компилируется babel. Это результат компиляции myModule:

var foo = function foo() {};
var bar = function bar() { foo(); };
exports.foo = foo;
exports.bar = bar;

При объявлении функции bar ссылка на функцию foo включается в объявление функции.

В вашей тестовой среде, когда вы импортируете foo и bar, вы действительно импортируете exports.foo и exports.bar.

Следовательно, когда вы издеваетесь foo, вы действительно издеваетесь exports.foo.

При выполнении bar() то, что вызывает bar, - это вложенная ссылка на foo.

Следовательно, тест не проходит правильно, поскольку exports.foo никогда не вызывается при выполнении bar()!

Решение (я)

Изучая информацию в Интернете, вы можете найти некоторые решения для преодоления этой проблемы с использованием функции require.

Теперь, чтобы быть точным, функция require не является частью стандартного API JavaScript. Это встроенная функция среды Node.js, предназначенная для загрузки модулей.

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

Единый источник правды

Первая стратегия, которую вы можете использовать, - это сохранение ссылок на ваши методы в объекте, который вы затем будете экспортировать. bar вызовет ссылку на foo, хранящуюся в этом объекте.

var foo = function foo() {};
var bar = function bar() { exportFunctions.foo(); };
const exportFunctions = {
  foo,
  bar
};
export default exportFunctions;

Таким образом, вы импортируете и имитируете одну и ту же ссылку на foo, который вызывается bar(), и тот же тест, определенный ранее, теперь пройдет!

Разделение проблем

С другой стороны, вы можете разделить проблемы вашего кода и объявить две функции в двух разных модулях.

// fooModule.js
export function foo () { ... }
// barModule.js
export function bar () { fooModule.foo() }

Это приведет к стандартному сценарию зависимости внешнего модуля.

Теперь вы можете адаптировать свой тест так, чтобы он был:

import foo from './fooModule';
import bar from './barModule';
test('calls fooModule.foo', () => {
  const fooSpy = jest.spyOn(fooModule, 'foo');
bar();
expect(fooSpy).toHaveBeenCalledTimes(1);
});

Я надеюсь, что эта статья окажется для вас полезной на пути к счастливой и чистой доставке кода!

Спасибо моим коллегам Саша и Бретт aka Je (s) tt за поддержку и приятно проведенное время вместе во время исследования этой темы!