如何在Vitest/Jest中抽离测试Mock至独立文件复用?
Vitest 中复用 Hook Mock 的正确姿势
我在使用兼容Jest API的Vitest进行React项目测试时,遇到了Hook Mock复用的问题:
现有文件结构:
// hooks/useEntities/useEntities.ts function useEntities() { // 该Hook会发送API请求,需要在测试中Mock }
// SomeComponent.tsx import useEntities from 'hooks/useEntities/useEntities' function SomeComponent () { const entities = useEntities() return <div>...</div> }
直接在测试文件里写vi.mock是可行的:
// SomeComponent.test.tsx vi.mock('hooks/useEntities/useEntities', () => ({ // 此方式有效 default: [...] })) describe('SomeComponent', () => { it('renders properly', () => { render(<SomeComponent />) }) })
但因为要Mock大量Hook,想把Mock逻辑抽离到独立文件复用,尝试了两种方式都失败了:
第一种:
// init.ts vi.mock('hooks/useEntities/useEntities', () => ({ default: [...] }))
// SomeComponent.test.tsx => 无效 await init() describe(...) { it(...) { render(<SomeComponent />) } }
第二种:
// init.ts vi.mock('hooks/useEntities/useEntities', () => ({ default: [...] }))
// SomeComponent.test.tsx => 无效 describe(...) { beforeAll(async () => { await init() await init(vi) // 尝试传入vi实例 }) it(...) { render(<SomeComponent />) } }
问题原因
Vitest的vi.mock是静态执行机制,它会在测试文件的所有代码(包括import语句)执行前就运行,所以不能放在异步函数、beforeAll这类生命周期钩子里面调用,必须让它在模块顶层执行才能生效。
正确的复用方案
方案1:集中式静态Mock(适合固定返回值的场景)
- 创建一个专门的Mock配置文件,在文件顶层直接编写所有Hook的Mock:
// test/mocks/hooks.ts import { vi } from 'vitest'; // 集中管理所有需要Mock的Hook vi.mock('hooks/useEntities/useEntities', () => ({ default: () => [/* 你的固定Mock返回值 */] })); // 可以继续添加其他Hook的Mock // vi.mock('hooks/useUser/useUser', () => ({ ... }))
- 在测试文件的最顶层导入这个配置文件(必须放在其他import之前):
// SomeComponent.test.tsx // 先导入Mock配置,确保静态Mock先执行 import '../test/mocks/hooks'; import { render } from '@testing-library/react'; import SomeComponent from './SomeComponent'; describe('SomeComponent', () => { it('renders correctly with mocked hook', () => { render(<SomeComponent />); // 你的断言逻辑 }); });
方案2:可动态修改的Mock(适合不同用例需要不同返回值的场景)
如果需要在不同测试用例里修改Hook的返回值,可以把Mock函数暴露出来:
- 在Mock配置文件中定义可修改的Mock函数并暴露:
// test/mocks/hooks.ts import { vi } from 'vitest'; // 创建一个可修改的Mock函数,设置默认返回值 export const mockUseEntities = vi.fn(() => [/* 默认返回值 */]); vi.mock('hooks/useEntities/useEntities', () => ({ default: mockUseEntities }));
- 在测试用例中导入并修改Mock函数:
// SomeComponent.test.tsx import '../test/mocks/hooks'; import { mockUseEntities } from '../test/mocks/hooks'; import { render } from '@testing-library/react'; import SomeComponent from './SomeComponent'; describe('SomeComponent', () => { // 每个用例前重置Mock状态,避免用例间互相影响 beforeEach(() => { mockUseEntities.mockReset(); }); it('显示空状态当没有数据时', () => { mockUseEntities.mockReturnValue([]); render(<SomeComponent />); // 断言空状态的逻辑 }); it('渲染实体列表当有数据时', () => { mockUseEntities.mockReturnValue([{ id: 1, name: '测试实体' }]); render(<SomeComponent />); // 断言列表渲染的逻辑 }); });
内容的提问来源于stack exchange,提问作者Matan Gubkin
相关产品推荐
相关产品推荐

