Невозможно правильно высмеять модуль машинописного текста в шутку

У меня есть тест, который я пытаюсь запустить, и я хочу имитировать модуль с помощью 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.

Этот вопрос похож на: Как имитировать функции в одном модуле с помощью Jest?. Если вы считаете, что это другое, отредактируйте вопрос, поясните, чем он отличается и/или как ответы на этот вопрос не помогают решить вашу проблему.

Brother58697 09.08.2024 06:50

@Brother58697, какое решение ты бы выбрал? Ответ нежизнеспособен. И следующий по количеству голосов ответ тоже нежизнеспособен. Экспорт не будет разбиваться на разные файлы. Согласно шутливым документам, это должно работать. Я мог бы показать вам сотни рабочих примеров в моем текущем проекте, особенно техники spyOn. Поэтому любопытно, что именно этот экземпляр не работает. Я предполагал, что это сработает, когда урезал его до такой степени после удаления всего кода, специфичного для приложения.

kamcknig 09.08.2024 17:46

Теперь я вижу в вашем редактировании, что вы пришли к тому же решению до того, как я написал свой комментарий. Прочитайте первые несколько комментариев в выпуске github, чтобы понять, почему это происходит, в противном случае я рад, что теперь это работает.

Brother58697 09.08.2024 20:22

@Brother58697Brother58697, не стесняйтесь писать ответ, и я приму его. Спасибо, я, конечно, не смог найти эту статью сам, и я даже просмотрел их github, прежде чем опубликовать здесь.

kamcknig 12.08.2024 16:33
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
5
53
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

У вас возникли проблемы с издевательством 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 дополнительную степень гибкости, не слишком сильно меняя общий рабочий процесс, и значительно упрощает тестирование.

Другие вопросы по теме