Допустим, у вас есть файл:
// f.js export function b(){ return 'b'; } export function a(){ return b(); }
Если вы хотите издеваться над b
, чтобы проверить a, что ж ... Это не так просто, как кажется.
Наивный подход
Первоначально я пытался сделать следующее:
test('a', () => { const f = require('./f'); jest.spyOn(f, 'b').mockReturnValue('c'); expect(f.a()).toBe('c'); // FAILED! // expected 'c' got 'b' })
Нет ... Не пойдет.
Это связано с тем, что экспортированный b
действительно является издевательским, но это не тот b
, который вызывается a
внутри модуля.
Решение 1. Разделение модуля на разные файлы
Если вы переместите b
в отдельный файл:
// b.js export function b(){ return 'b'; } // f.js import {b} from './b'; export function a(){ return b(); }
Тогда тест пройдёт:
test('a', () => { const b = require('./b'); const f = require('./f'); jest.spyOn(b, 'b').mockReturnValue('c'); expect(f.a()).toBe('c'); //PASSED! })
Это выглядит как самое чистое решение, но что, если вы хотите сохранить свои функции в одном файле?
Решение 2. Вызов имитационной функции с использованием экспорта
Добавьте exports.
перед вызовом функции
// f.js export function b(){ return 'b'; } export function a(){ return exports.b(); }
И тогда тест просто пройдет:
test('a', () => { const f = require('./f'); jest.spyOn(f, 'b').mockReturnValue('c'); expect(f.a()).toBe('c'); //PASSED! })
Мне очень нравится это решение, потому что в тестируемом файле вы меняете только что-то несущественное. Более того, даже этого небольшого изменения можно избежать:
Вы можете использовать библиотеку babel-plugin-explicit-exports-references
, чтобы программно добавить exports.
ко всем функциям в одном модуле.
Обратите внимание, что библиотека очень свежая и имеет очень небольшую аудиторию.
Используйте ее с осторожностью с точки зрения безопасности.
Стоит упомянуть неоптимальное решение - экспорт объекта пространства имен
Вы можете создать пространство имен, которое экспортируете как объект по умолчанию, и вызовите b
, используя пространство имен.
Таким образом, когда вы вызываете jest.mock
, он заменяет функцию b
в объекте пространства имен.
// f.js const f = { b(){ return 'b'; }, a(){ return f.b(); } }; export default f;
И тогда тест пройдёт:
test('a', () => { const f = require('./f'); jest.spyOn(f, 'b').mockReturnValue('c'); expect(f.a()).toBe('c'); //PASSED! })
Но тогда вам нужно будет импортировать его как default
импорт, не имея возможности разбить его на именованные экспорты.
// won't work: // import {a} from './f'; import f from './f'; ... f.a();
Что довольно некрасиво.
Другое неоптимальное решение - использование библиотеки переподключения
Библиотека babel-plugin-rewire - это навязчивая библиотека, которая меняет то, что находится внутри модулей.
Кажется, это не очень поддерживаемая библиотека, и на самом деле она не сработала для меня, потому что не поддерживает TypeScript, но вот примерно то, как она должна работать:
Вам не нужно ничего менять в тестируемом файле.
// f.js export function b(){ return 'b'; } export function a(){ return b(); }
Перепрограммируйте функцию b
в тесте:
import {a,__set__} from './f';
test('a', () => { const f = require('./f');// This rewires b to return 'c' __set__(
'b', () => 'c'); expect(f.a()).toBe('c'); // PASSED! })
Удачного тестирования :)