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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 20:04:56