关键在于用vi.mock拦截动态import路径并返回模拟组件,再调用vi.dynamicImportSettled()确保加载完成,结合路由mock实现可控测试。

在 Vue 3 单元测试中 mock 异步组件,关键不是“替换组件本身”,而是控制 defineAsyncComponent 内部的动态 import 行为,让其返回可预测、可断言的模拟组件,同时确保加载时机可控。Vitest 提供了原生支持机制,无需重写组件逻辑。
用 vi.mock 拦截动态 import 路径
动态 import 是一个函数调用,Vitest 允许你对模块路径做精确拦截。比如组件里写了:
const Content = defineAsyncComponent(() => import(`@/pages/${route.params.path}.md`))你可以在测试前 mock 对应路径的模块导出:
- 在 test 文件顶部或
beforeEach中使用vi.mock('@/pages/home.md', () => ({ default: { render: () => h('div', 'mocked home') } })) - 路径必须完全匹配(含扩展名),否则拦截失败
- 返回对象需包含
default导出,且结构符合 Vue 组件规范(如含render或setup)
等待异步加载完成再断言
即使 mock 了模块,defineAsyncComponent 仍会走异步流程。直接渲染后立即查找内容,大概率找不到——因为组件还没加载完。
立即学习“前端免费学习笔记(深入)”;
- 调用
await vi.dynamicImportSettled()确保所有 pending 的动态 import 已 resolve - 配合
await waitFor(() => expect(screen.getByText('mocked home')).toBeInTheDocument())等待 DOM 更新 - 避免使用
await nextTick(),它不保证 import 完成,只保证当前微任务队列清空
路由参数影响加载时,要同步 mock 路由
如果异步组件依赖 useRoute().params.path 动态决定加载哪个模块,光 mock 模块不够,还得让路由返回预期值。
- 用
createRouter+createMemoryHistory构建测试路由实例 - 在挂载组件时传入
plugins: [router],并提前router.push('/home') - 或者更轻量:用
vi.mock('vue-router', () => ({ useRoute: vi.fn(() => ({ params: { path: 'home' } })) }))
避免在组件内硬编码 mock 逻辑
不要在组件 setup 里写 if (import.meta.env.DEV) {...} 来切换真实 import 和 mock 数据——这会让测试逻辑和业务逻辑耦合,难以覆盖边界场景。
- mock 行为应全部放在测试文件中,保持组件纯净
- 若需统一管理 mock 行为(如按环境开关),可在 vite.config 或测试 setup 文件中集中配置
vi.mock规则 - 真实项目中建议用运行时代理层(如
useApi)隔离数据源,但单元测试阶段直接 mock 模块更直接、更可靠


















