用途
@vue/test-utils 是 Vue 3 的组件测试工具库,用于在测试中挂载组件、操作 DOM、查询子组件并断言渲染结果与事件。资料说明其与测试运行器无关,可用于 Vitest、Jest 或其它运行器,但需要一个类浏览器的 DOM 环境,例如 jsdom 或 happy-dom。该库需要 Vue 3.x 与 @vue/compiler-dom 3.x 作为 peer 依赖;@vue/server-renderer 3.x 是可选 peer 依赖,仅在需要 renderToString 时使用。
安装与快速开始
安装命令:npm install @vue/test-utils --save-dev
资料给出的快速示例:
import { mount } from '@vue/test-utils'
import Counter from './Counter.vue'
test('increments', async () => {
const wrapper = mount(Counter, { props: { start: 1 } })
await wrapper.find('button').trigger('click')
expect(wrapper.text()).toContain('2')
})
主要能力
资料列出的公开 API 包括:
- mount:挂载组件,返回 VueWrapper。
- shallowMount:与 mount 相同,但会桩掉所有子组件。
- renderToString:将组件 SSR 渲染为字符串,返回 Promise<string>。
- flushPromises:等待挂起的非 Vue promise,例如被模拟的 API 调用、计时器。
- enableAutoUnmount(hook):通过测试钩子自动卸载所有 wrapper。
- disableAutoUnmount:停止自动卸载。
- VueWrapper:已挂载组件实例的包装类。
- DOMWrapper:DOM 元素的包装类,例如 new DOMWrapper(document.body)。
- RouterLinkStub:用于 <router-link> 的桩组件。
- config:共享的默认挂载选项与 wrapper 插件。
- createWrapperError:内部函数,用于生成 find 未命中时返回的错误 wrapper。
使用方式与核心规则
资料列出的核心规则包括:
- 对返回 promise 的方法都要 await:trigger、setValue、setProps、setData、renderToString、flushPromises。否则断言会在 DOM 更新前执行。
- 元素必须存在时使用 get() / getComponent(),未命中会抛错;元素可能不存在时使用 find() / findComponent(),未命中返回 exists() 为 false 的错误 wrapper。
- find() 只接受 CSS 选择器。查找子组件需使用 findComponent(Component)、findComponent({ name: 'Foo' })、findComponent({ ref: 'foo' }) 或 CSS 选择器。
- 在测试初始化中注册一次 enableAutoUnmount(afterEach),防止测试间状态泄漏。
- Vue 不跟踪的 promise(例如被模拟的 HTTP 客户端、setTimeout)使用 flushPromises()。
- wrapper.vm 只可靠暴露组件实际暴露的内容:选项式 API 状态、defineExpose() 绑定(自 2.5.0 起,PR #2927)或 setup() 返回值。
示例:
import { enableAutoUnmount } from '@vue/test-utils'
import { afterEach } from 'vitest'
enableAutoUnmount(afterEach)
版本说明(2.4.10 到 2.5.1)
- 破坏性变更(2.5.0):移除类组件支持。应挂载由 defineComponent、选项对象或 <script setup> 单文件组件定义的 Vue 组件。
- 修复(2.5.0):子组件卸载时会清空其 emitted() 历史,应在卸载前断言事件。
- 修复(2.5.0):defineExpose 绑定可在 findComponent(...).vm 上看到。
- 修复(2.4.11):setData() 对同时使用 setup() 与 data() 的组件可正常工作。
- 修复(2.4.11):trigger('keydown') 会设置符合规范的 event.code。
- 类型(2.4.11):导出 GlobalMountOptions。
更早的 v2 里程碑:renderToString 在 2.3.0 加入用于 SSR 测试;html({ raw: true })、指令桩(vName: true)、多选 setValue 数组在 2.2.0 加入;enableAutoUnmount / disableAutoUnmount 取代 v1 的 enableAutoDestroy。
其它说明:propsData 仍可用但已废弃,应使用 props;stubs 接受记录对象(如 { Foo: true })或名称数组(如 ['Foo'])。
从 v1(Vue 2)迁移
资料给出的迁移要点:
- propsData 改为 props;createLocalVue 移除,改用 global.plugins / global.mixins。
- mocks、stubs、provide、directives 移到 global 下。
- destroy() 改为 unmount();findAll().at(i) 改为 findAll()[i](返回数组)。
- createWrapper() 移除,改用 new DOMWrapper(el)。
- setChecked / setSelected 移除,合并到 setValue。
- find() 不再按名称查找组件,应使用 findComponent。
- shallowMount 不再渲染桩的默认插槽内容,可通过 config.global.renderStubDefaultSlot = true 恢复。
- 移除项:is、isEmpty、isVueInstance、name、setMethods、contains、scopedSlots(已合并到 slots)。
最佳实践
资料给出的实践建议包括:
- 优先使用 mount 加针对性的 global.stubs,而不是 shallowMount;浅层测试断言的是结构而非行为,且被桩掉的子组件会隐藏真实交互。
- 如果确实要广泛使用桩,设置 config.global.renderStubDefaultSlot = true,让桩的默认插槽内容仍被渲染。
- 用 vName 键来桩指令:global.stubs: { vTooltip: true },或传入替代指令对象。
- <transition> 与 <transition-group> 默认被桩;也支持自定义过渡桩。
- 通过 global.provide 传入 inject 值,与生产注入保持一致;对于带类型的注入键,可包装键:provide: { [injectionKey as symbol]: value }。
- 测试组合式函数时,挂载一个最小宿主组件并从 wrapper.vm 读取状态。
- 对带 async setup() 的组件,先包在 <Suspense> 宿主中再挂载。
- 对 <Teleport>,可以桩掉它(global.stubs: { teleport: true }),或在 beforeEach 中创建目标元素并用 document.querySelector 查询。
- 在不安装路由的情况下测试 <router-link> 周边逻辑时,使用 RouterLinkStub:global.stubs: { 'router-link': RouterLinkStub }。
适用场景与限制
适用场景:Vue 3 组件的单元测试与集成测试,包括表单与 v-model、事件断言、路由、Vuex/Pinia、Suspense、Teleport、SSR 等测试场景;资料提到测试配方涵盖这些方向。
限制:需要 Vue 3.x 与 @vue/compiler-dom 3.x;需要类浏览器 DOM 环境;2.5.0 起不再支持类组件;wrapper.vm 只可靠暴露组件实际暴露的内容;renderToString 需要可选的 @vue/server-renderer。