У меня есть тест, который я пытаюсь запустить, и я хочу имитировать модуль с помощью jest.mock. Однако мне не удается заставить этот метод работать: в приведенном ниже примере всегда вызывается исходный метод serializeProduct
. Я пробовал шпионить, я пробовал издеваться над модулем. Когда я вывожу из системы переменные, которые являются издевательствами/шпионами, они действительно являются фиктивными функциями, по крайней мере, так что издевательство происходит. Просто что-то заставляет его вызывать этот оригинальный метод вместо макета.
Product.serializer.ts
import { ProductDto } from './product.dto';
export function serializeProducts(value: any[]): ProductDto[] {
return value.map(serializeProduct);
}
export function serializeProduct(value: any) {
return value;
}
Product.serializer.spect.ts в той же папке, что и выше.
import { expect } from '@jest/globals';
import * as productsSerializer from './products.serializer';
describe('serializer', () => {
afterEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
});
describe('serializeProducts method', () => {
it('should foo', () => {
const packages = [1,2,3,];
const spy = jest.spyOn(productsSerializer, 'serializeProduct').mockImplementation(() => undefined);
productsSerializer.serializeProducts(packages);
expect(spy).toHaveBeenCalledTimes(3);
});
});
});
Я также пробовал издеваться над модулем изначально вот так
jest.mock('./products.serializer', () => ({
...jest.requireActual('./products.serializer'),
serializeProduct: jest.fn()
});
import { expect } from '@jest/globals';
import * as productsSerializer from './products.serializer';
describe('serializer', () => {
afterEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
});
describe('serializeProducts method', () => {
it('should foo', () => {
const packages = [1,2,3,];
(productsSerializer.serializeProduct as jest.Mock).mockImplementation(() => undefined);
productsSerializer.serializeProducts(packages);
expect(productsSerializer.serializeProduct).toHaveBeenCalledTimes(3);
});
});
});
Я также отдельно импортировал методы из приведенного выше примера вместо импорта * as
, но безрезультатно.
У меня есть примеры того, как это работает в том же проекте, над которым я работаю, но в этом нет.
РЕДАКТИРОВАТЬ Принятое решение по предложенному другому вопросу нежизнеспособно, поскольку требует изменения кода приложения для исправления теста.
Другое решение — переместить экспорт, а также изменить код приложения. Я подтвердил, что если я удалю один из методов, он сработает, но на самом деле это тоже не должно быть жизнеспособным решением. Для кого-то это может сработать, но в контексте моего приложения это не имеет смысла.
Поэтому я хотел бы выяснить, почему это не работает. Если это сработает, то должно быть решение, не требующее изменения кода приложения.
РЕДАКТИРОВАТЬ 2 Я обнаружил, что преобразование методов в экспортированные константы и определение их как стрелочных функций решает проблему. Это означает, что это, вероятно, как-то связано с импортом jest/node.
@Brother58697, какое решение ты бы выбрал? Ответ нежизнеспособен. И следующий по количеству голосов ответ тоже нежизнеспособен. Экспорт не будет разбиваться на разные файлы. Согласно шутливым документам, это должно работать. Я мог бы показать вам сотни рабочих примеров в моем текущем проекте, особенно техники spyOn. Поэтому любопытно, что именно этот экземпляр не работает. Я предполагал, что это сработает, когда урезал его до такой степени после удаления всего кода, специфичного для приложения.
Вот интересная тема по этому вопросу. Но наименее навязчивое решение, которое я только что попробовал из ветки, — это использовать функции стрелок. Теперь это зависит от вашего трансформатора (поскольку вы найдете некоторые комментарии, которые не смогли заставить его работать). Тот, который у меня получился «из коробки», — это ts-jest. Так что используйте export const serialize = () => {}
, настройте ts-jest и дайте мне знать, если это сработает (да, это только что сработало для меня), если да, я напишу ответ
Теперь я вижу в вашем редактировании, что вы пришли к тому же решению до того, как я написал свой комментарий. Прочитайте первые несколько комментариев в выпуске github, чтобы понять, почему это происходит, в противном случае я рад, что теперь это работает.
@Brother58697Brother58697, не стесняйтесь писать ответ, и я приму его. Спасибо, я, конечно, не смог найти эту статью сам, и я даже просмотрел их github, прежде чем опубликовать здесь.
У вас возникли проблемы с издевательством serializeProduct
, потому что serializeProducts
вызывает его из того же модуля. Основная причина этой проблемы заключается в том, как передается код.
Давайте посмотрим на следующий пример:
export function foo() {};
export function bar() { foo() };
Ваш транспилятор, скорее всего, преобразует его во что-то вроде этого (упрощенно):
function foo() {};
function bar() { foo() };
exports.foo = foo;
exports.bar = bar;
Проблема здесь в том, что мы имитируем (переназначаем) exports.foo
в наших тестах, в то время как bar
вызывает оригинал foo
.
Одно решение
Итак, единственное, что мы можем сделать, это вызвать экспортированное свойство foo
, иначе говоря, exports.foo
, чтобы, когда оно будет имитировано, вместо этого мы вызывали макет.
export function foo() {};
export function bar() { exports.foo() };
Это действительный код, и он будет правильно транспилирован, но он некрасив.
Лучшее решение для вас
Поскольку вы используете Typescript, велика вероятность, что вы используете ts-jest
в качестве преобразователя. У ts-jest
есть небольшая приятная особенность: он заменяет экспортированные переменные их аналогами-членами exports
.
Таким образом, вместо вызова функции, которую мы определяем с помощью оператора функции, нам просто нужно вызвать экспортированную переменную, назначенную нужной нам функции, и эта переменная будет издевательской из-за полезных подстановок ts-jest
. Мы можем сделать это разными способами:
function foo () {};
// All 3 of these are replaced by `exports.<name>` by `ts-jest` where they're called
export const foo1 = foo; // Reassign to a new variable
export const foo2 = function() {} // Anonymous function
export const foo3 = () => {} // Arrow function
Вот небольшая иллюстрация, которую я получил при отладке теста, который запускал локально. Давайте посмотрим исходный модуль и то, как его преобразовал ts-jest
.
// Original
export function foo() {};
export const bar = function() {}
export function buzz() {
foo();
bar();
}
// Transformed
Object.defineProperty(exports, "__esModule", { value: true });
exports.bar = void 0;
exports.foo = foo;
exports.buzz = buzz;
function foo() {};
const bar = () => {};
exports.bar = bar;
function buzz() {
foo();
(0, exports.bar)();
}
Обратите внимание на то, как в buzz
вызов функции bar
был заменен ее аналогом exports
. Теперь мы можем без проблем использовать spyOn
.
Больше чтения: Проблема на GitHub, посвященная именно этой ситуации.
Поощрительное упоминание — внедрение зависимостей
В вашем конкретном случае serializeProducts
по сути является оберткой вокруг Array.map
, что делает его основным кандидатом для некоторого внедрения зависимостей. Вы можете дать ему второй параметр, передать его serializeProduct
по умолчанию, а затем передать ему фиктивную функцию в своих тестах.
Это дает serializeProducts
дополнительную степень гибкости, не слишком сильно меняя общий рабочий процесс, и значительно упрощает тестирование.
Этот вопрос похож на: Как имитировать функции в одном модуле с помощью Jest?. Если вы считаете, что это другое, отредактируйте вопрос, поясните, чем он отличается и/или как ответы на этот вопрос не помогают решить вашу проблему.