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

jest.spyOn对导入函数失效:预期Mock函数被调用却未触发

解决Jest spyOn Mock默认导出函数失效的问题

我太懂这种头疼的感觉了——明明写了jest.spyOn去mock默认导出的函数,结果却一直报“Expected mock function to have been called, but it was not called”的错误。咱们先拆解下问题根源,再给你几个实用的解决办法。

问题到底出在哪?

你当前的代码里,testFn在模块加载阶段就直接绑定了foo模块默认导出的原始函数引用。而你在测试代码里用jest.spyOn(foo, 'default'),其实只是给当前测试模块里的foo.default加了个spy,但testFn内部用的还是它自己导入的那个原始foo函数——两者根本不是同一个引用,spy自然捕捉不到调用。

靠谱解决方案

这里有几种不同的思路,你可以根据自己的代码场景选:

方法1:用jest.mock直接mock整个foo模块

这是最直接有效的方式,因为jest.mock会在模块加载的最早期就替换掉foo模块的导出内容,这样testFn从一开始导入的就是mock后的函数:

// 测试文件
jest.mock('foo', () => ({
  default: jest.fn() // 把默认导出替换成Jest mock函数
}));

import testFn from 'test';
import foo from 'foo';

test('验证默认导出函数被调用', () => {
  testFn();
  expect(foo.default).toHaveBeenCalledWith('text'); // 还能顺便验证传入的参数
});

方法2:调整模块结构,让依赖引用可被Spy

如果不想用jest.mock,可以修改test.js的结构,把foo的引用改成动态可替换的形式:

修改test.js:

// test.js
import * as fooModule from 'foo'; // 导入整个模块对象
export default () => fooModule.default('text'); // 使用模块上的default属性

测试文件:

import testFn from 'test';
import * as fooModule from 'foo';

test('验证Spy捕捉到函数调用', () => {
  const spyFn = jest.spyOn(fooModule, 'default');
  testFn();
  expect(spyFn).toHaveBeenCalled();
  spyFn.mockRestore(); // 测试结束后记得清理mock
});

这样修改后,testFn使用的是fooModule.default这个动态引用,你在测试里用spyOn替换它时,testFn调用的就是被Spy后的函数了。

方法3:依赖注入(适合复杂场景)

如果testFn的逻辑允许,可以把foo作为可选参数传入,测试时直接传入mock函数就行:

修改test.js:

// test.js
import foo from 'foo';
export default (fn = foo) => fn('text'); // 默认使用foo,允许传入自定义函数

测试文件:

import testFn from 'test';

test('验证函数被调用', () => {
  const mockFoo = jest.fn();
  testFn(mockFoo);
  expect(mockFoo).toHaveBeenCalledWith('text');
});

这种方式最灵活,完全绕开了模块导入带来的引用绑定问题。

总结

核心问题就是ES模块的静态导入导致引用固定,jest.spyOn无法影响已经导入的函数引用。要么用jest.mock在模块加载阶段替换导出,要么调整模块结构让引用可动态修改,或者用依赖注入的方式彻底绕过这个问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 07:56:33