如何在Vitest中仅对单个测试Mock模块函数,其余用实际实现
在Vitest中混合使用真实实现与Mock测试工具函数
场景代码
我有两个工具函数:callMethodIf 会调用并返回 blackbox 函数的结果:
~~/utils/call-method-if.js
import { blackbox } from '~~/utils/blackbox'; export const callMethodIf = (condition) => { return blackbox(condition); };
~~/utils/blackbox.js
export const blackbox = (condition) => { return { called: condition }; };
需求
我需要实现两个测试用例:
- 一个测试调用
blackbox的真实实现 - 另一个测试使用
Mock后的blackbox返回值
尝试过的写法及问题
写法1:测试块内Mock
单独运行每个测试都正常,但组合运行时失败,调用 vi.clearAllMocks() 或 vi.resetAllMocks() 也无法解决:
import { describe, expect, it } from 'vitest'; import { callMethodIf } from '~~/utils/call-method-if'; describe('Call method if', () => { it('returns "called: true" if condition is true', () => { const result = callMethodIf(true); expect(result).toEqual({ called: true }); }); it('returns mocked blackbox return object', () => { vi.mock('~~/utils/blackbox', () => ({ blackbox: vi.fn().mockReturnValue({ mock: true }) })); const result = callMethodIf(false); expect(result).toEqual({ mock: true }); }); });
写法2:全局Mock后尝试覆盖
全局定义Mock后,在第一个测试中尝试覆盖为真实实现,同样无效:
import { describe, expect, it } from 'vitest'; import { callMethodIf } from '~~/utils/call-method-if'; vi.mock('~~/utils/blackbox', () => ({ blackbox: vi.fn().mockReturnValue({ mock: true }) })); describe('Call method if', () => { it('returns "called: true" if condition is true', () => { vi.mock('~~/utils/blackbox', async () => ({ blackbox: (await vi.importActual('~~/utils/blackbox')).blackbox })); const result = callMethodIf(true); expect(result).toEqual({ called: true }); }); it('returns mocked blackbox return object', () => { const result = callMethodIf(false); expect(result).toEqual({ mock: true }); }); });
正确解决方案
问题出在 vi.mock 是静态执行的,会在模块加载阶段就生效,测试块内的 vi.mock 不会改变已加载的模块。需要结合 vi.resetModules() 和动态导入,或者使用 vi.doMock 来动态控制Mock:
方案1:每个测试用例重置模块并动态导入
import { describe, expect, it, vi } from 'vitest'; describe('Call method if', () => { // 每个测试前重置模块,避免相互影响 beforeEach(() => { vi.resetModules(); }); it('returns "called: true" if condition is true', async () => { // 直接导入真实模块 const { callMethodIf } = await import('~~/utils/call-method-if'); const result = callMethodIf(true); expect(result).toEqual({ called: true }); }); it('returns mocked blackbox return object', async () => { // 先Mock模块,再导入 vi.doMock('~~/utils/blackbox', () => ({ blackbox: vi.fn().mockReturnValue({ mock: true }) })); const { callMethodIf } = await import('~~/utils/call-method-if'); const result = callMethodIf(false); expect(result).toEqual({ mock: true }); // 可选:验证Mock被调用 const { blackbox } = await import('~~/utils/blackbox'); expect(blackbox).toHaveBeenCalledWith(false); }); });
方案2:使用 vi.importActual 切换真实实现
如果希望全局Mock,在特定测试中切换回真实实现,可以这样写:
import { describe, expect, it, vi } from 'vitest'; // 全局Mock blackbox vi.mock('~~/utils/blackbox', () => ({ blackbox: vi.fn().mockReturnValue({ mock: true }) })); describe('Call method if', () => { it('returns "called: true" if condition is true', async () => { // 获取真实的blackbox实现 const { blackbox: actualBlackbox } = await vi.importActual('~~/utils/blackbox'); // 导入并替换Mock的实现 const { blackbox } = await import('~~/utils/blackbox'); blackbox.mockImplementation(actualBlackbox); const { callMethodIf } = await import('~~/utils/call-method-if'); const result = callMethodIf(true); expect(result).toEqual({ called: true }); }); it('returns mocked blackbox return object', async () => { const { callMethodIf } = await import('~~/utils/call-method-if'); const result = callMethodIf(false); expect(result).toEqual({ mock: true }); }); });
关键说明
vi.resetModules():在每个测试前重置已加载的模块,确保每次导入都是全新的实例vi.doMock():动态Mock模块,会影响后续的模块导入vi.importActual():获取模块的真实实现,用于临时覆盖Mock
内容的提问来源于stack exchange,提问作者floriankapaun
相关产品推荐
相关产品推荐

