You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jest无法正确Mock TypeScript模块问题排查

Jest Mock TypeScript模块失效:原方法始终被调用

问题概述

测试TypeScript模块时,尝试用jest.spyOn或jest.mockMock同模块内的serializeProduct方法,但实际执行serializeProducts时仍会调用原方法,即使日志显示Mock函数已生成。项目中其他同类Mock正常,仅该案例失败。已知修改业务代码(如改为箭头函数常量)可解决,但业务代码不可修改,也无法调整导出顺序。

相关代码

业务代码(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;
}

测试尝试1(spyOn方式)

import { expect } from '@jest/globals';
import * as productsSerializer from './products.serializer';

describe('serializer', () => {
    afterEach(() => {
        jest.clearAllMocks();
        jest.restoreAllMocks();
    });

    describe('serializeProducts method', () => {
        it('should call serializeProduct 3 times', () => {
            const packages = [1,2,3];

            const spy = jest.spyOn(productsSerializer, 'serializeProduct').mockImplementation(() => undefined);

            productsSerializer.serializeProducts(packages);

            expect(spy).toHaveBeenCalledTimes(3);
        });
    });
});

测试尝试2(模块Mock方式)

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 call serializeProduct 3 times', () => {
            const packages = [1,2,3];

            (productsSerializer.serializeProduct as jest.Mock).mockImplementation(() => undefined);

            productsSerializer.serializeProducts(packages);

            expect(productsSerializer.serializeProduct).toHaveBeenCalledTimes(3);
        });
    });
});

问题原因

核心在于模块内部函数的引用机制:

  • serializeProducts内部直接调用同模块的serializeProduct时,引用的是模块内部的原始函数实例,而非导出对象上的函数引用。
  • Jest的jest.spyOn和jest.mock仅能修改模块导出对象上的函数引用,无法影响模块内部直接调用的原始函数。
  • 改成箭头函数常量后生效,是因为箭头函数作为导出的常量,模块内部调用时会通过导出对象间接引用,此时Mock修改导出对象的引用就能生效。

无需修改业务代码的解决方案

方案:重新Mock模块内的serializeProducts,让它调用导出的Mock函数

在jest.mock中重新实现serializeProducts,使其调用导出的serializeProduct(即我们的Mock函数),而非模块内部的原始函数:

jest.mock('./products.serializer', () => {
  const actual = jest.requireActual('./products.serializer');
  return {
    ...actual,
    serializeProduct: jest.fn(),
    // 重新实现serializeProducts,让它调用当前模块导出的serializeProduct
    serializeProducts: (value: any[]) => value.map(item => (module.exports as any).serializeProduct(item))
  };
});

import { expect } from '@jest/globals';
import * as productsSerializer from './products.serializer';

describe('serializer', () => {
    afterEach(() => {
        jest.clearAllMocks();
    });

    describe('serializeProducts method', () => {
        it('should call serializeProduct exactly 3 times', () => {
            const packages = [1,2,3];
            (productsSerializer.serializeProduct as jest.Mock).mockReturnValue(undefined);

            productsSerializer.serializeProducts(packages);

            expect(productsSerializer.serializeProduct).toHaveBeenCalledTimes(3);
        });
    });
});

此方案通过在Mock中重写serializeProducts,让它间接引用导出的serializeProduct,从而使Mock能够生效,完全不需要修改业务代码。

内容的提问来源于stack exchange,提问作者kamcknig

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 00:00:56