Vitest中vi.spyOn无法监控模块内副作用函数调用?求解决方案
Vitest中同模块函数Spy失效的解决办法
问题场景
模块代码aModule.ts:
export function a() { return sideEffect(); } export function sideEffect() { return 'a'; }
测试代码里想用vi.spyOn监控sideEffect是否被a调用,但实际运行时mock完全不生效,返回值还是原始的'a',调用次数也没被统计到——这在Jest里能正常工作,但Vitest里不行。
测试代码:
import { vi, expect, test } from 'vitest'; import * as aModule from '../src/aModule'; test('expect "sideEffect" to be called at least once', async () => { const sideEffectSpy = vi.spyOn(aModule, 'sideEffect').mockReturnValue('b'); const aSpy = vi.spyOn(aModule, 'a'); const res = aModule.a(); expect(res).toBe('b'); // 失败:返回'a' expect(sideEffectSpy).toHaveBeenCalled(); // 失败:未被调用 });
根本原因
函数a内部直接调用的是模块本地的sideEffect函数引用,而vi.spyOn是给**模块导出对象上的sideEffect**加了mock。这俩不是同一个引用,所以a执行时根本不会触发你设置的spy。
三种解决办法
办法1:修改业务代码,通过模块自身引用调用函数
把a里的直接调用改成通过模块对象调用,这样就会用到导出的版本:
// aModule.ts import * as self from './aModule'; export function a() { return self.sideEffect(); // 改成通过self调用 } export function sideEffect() { return 'a'; }
改完之后再跑测试,spy就能正常捕获调用了。
办法2:用vi.mock重写整个模块(无需改业务代码)
如果不想动业务代码,直接在测试里mock整个模块的导出:
// 测试文件 import { vi, expect, test } from 'vitest'; import * as aModule from '../src/aModule'; // 手动mock模块:保留原模块其他内容,只替换sideEffect vi.mock('../src/aModule', () => { const original = vi.importActual('../src/aModule'); return { ...original, sideEffect: vi.fn().mockReturnValue('b') }; }); test('expect "sideEffect" to be called at least once', async () => { const res = aModule.a(); expect(res).toBe('b'); expect(aModule.sideEffect).toHaveBeenCalled(); });
这种方式会让模块导出的sideEffect变成mock函数,而a执行时会间接用到这个导出版本(Vitest的模块mock会替换内部引用链)。
办法3:用Babel插件重写内部引用(适合Babel项目)
如果你的项目用Babel,可以安装babel-plugin-rewire,它能让你在测试中重写模块内部的函数引用。配置好插件后,就能直接mock内部函数而不用改业务代码,不过需要额外维护Babel配置。
内容的提问来源于stack exchange,提问作者micurs
相关产品推荐
相关产品推荐

