Vitest测试中Mock自定义Hook未返回预期值问题
问题重现
我有三个文件:
- use-user-info.ts
- user-info.tsx
- user-info.test.tsx
想要测试user-info.tsx中的UserInfo组件,在测试文件中Mock了useUserInfo Hook:
vi.mock("@/use-user-info"); describe(() => { beforeEach(() => { vi.mocked("@/use-user-info").mockReturnValue({ userId: 1, username: "John Doe", isLoading: false }) return () => {vi.clearAllMock()} }) })
遇到的问题:在beforeEach作用域中调用该Hook能拿到Mock值,但user-info.tsx中通过console.log查看Hook返回值时,得到的却是Hook的默认返回值。尝试了vi.mock、vi.doMock、mockReturnValue、mockImplementation等多种Mock方式,其他类似Hook也存在相同问题。
use-user-info.ts的实现:
export const useUserInfo = (userId) => { const { infoNotification } = useNotification(); const { updateUser } = useUpdateUser(updateBudgetData) const userMessages = useUserMessages(userId); return { infoNotification, updateUser, userMessages } }
问题原因及解决方案
1. Mock路径与组件导入路径不匹配
Vitest是根据导入路径匹配Mock的,如果组件中导入useUserInfo的路径和测试文件中vi.mock的路径不一致,Mock就不会生效。比如组件用相对路径../hooks/use-user-info,测试却用别名@/use-user-info,两者会被视为不同模块,导致Mock失效。
解决:
检查user-info.tsx中useUserInfo的导入语句,确保测试文件中vi.mock的路径和它完全一致。若使用路径别名,需确保Vitest配置中正确解析别名(参考下文配置示例)。
2. 错误使用vi.mocked
vi.mocked应该包裹导入的Hook函数,而非直接包裹模块路径。之前的写法vi.mocked("@/use-user-info")是错误的,正确做法是先导入Hook,再用vi.mocked包裹这个导入的函数。
修正后的测试代码示例:
// 先导入需要Mock的Hook和组件 import { useUserInfo } from "@/use-user-info"; import UserInfo from "@/user-info"; // 顶层调用vi.mock,会自动被提升到文件顶部 vi.mock("@/use-user-info"); describe("UserInfo Component", () => { beforeEach(() => { // Mock返回值必须和实际Hook的返回结构一致 vi.mocked(useUserInfo).mockReturnValue({ infoNotification: { add: vi.fn() }, // 模拟infoNotification的结构 updateUser: vi.fn(), // 用vi.fn()模拟函数类型属性 userMessages: [] // 模拟用户消息数据 }); }); afterEach(() => { // 注意是clearAllMocks(复数),清理所有Mock的调用记录 vi.clearAllMocks(); }); it("应该正确渲染Mock的用户数据", () => { render(<UserInfo />); // 这里添加你的断言逻辑,比如检查组件是否渲染了预期内容 }); });
3. Mock返回结构与实际Hook不匹配
你的useUserInfo返回的是包含infoNotification、updateUser、userMessages的对象,但之前的Mock返回的是userId、username、isLoading,结构完全不匹配。即使Mock生效,组件也无法拿到预期属性,甚至引发错误,也会让你误以为Mock未生效。
解决:
严格按照实际Hook的返回结构编写Mock返回值,确保每个属性都存在(用vi.fn()模拟函数类型属性,用合适默认值模拟其他属性)。
4. 路径别名未在Vitest中配置
如果使用@作为路径别名,需要在Vitest配置文件中明确配置别名解析,否则测试环境无法正确识别@/use-user-info这类路径,导致Mock失效。
Vitest配置示例(vitest.config.ts):
import { defineConfig } from 'vitest/config'; import react from '@vitejs/plugin-react'; import path from 'path'; export default defineConfig({ plugins: [react()], resolve: { alias: { '@': path.resolve(__dirname, './src') // 映射@到src目录 } }, test: { environment: 'jsdom', // React组件测试需要jsdom环境 // 其他测试配置项 } });
5. Mock时机错误
vi.mock会被Vitest自动提升到文件顶部执行,所以不要在describe、it或条件语句中调用vi.mock,必须放在文件顶层,否则可能导致Mock未提前生效,组件加载时还是会引入原始Hook。
内容的提问来源于stack exchange,提问作者Julio Villanueva

