Vue 的测试体系离不开两个核心工具:Vitest(测试运行器)和 Vue Test Utils(官方组件测试工具库)。它们配合起来,可以让你像真实用户一样去渲染组件、触发交互、检查输出。下面分别讲三种最常用的测试类型。
渲染测试:验证组件是否正确渲染
渲染测试是最基础的测试,用来确认组件在给定 Props 和状态时,是否输出了预期的 DOM 结构或文本内容。
// 待测试组件 UserCard.vue
<template>
<div class="user-card">
<h3>{{ name }}</h3>
<span v-if="isAdmin" class="badge">管理员</span>
</div>
</template>
<script setup>
defineProps({
name: String,
isAdmin: Boolean
})
</script>
// UserCard.test.js
import { mount } from '@vue/test-utils'
import { describe, it, expect } from 'vitest'
import UserCard from './UserCard.vue'
describe('UserCard', () => {
it('渲染用户名', () => {
const wrapper = mount(UserCard, {
props: { name: 'Alice', isAdmin: false }
})
expect(wrapper.text()).toContain('Alice')
})
it('当 isAdmin 为 true 时显示管理员徽章', () => {
const wrapper = mount(UserCard, {
props: { name: 'Bob', isAdmin: true }
})
expect(wrapper.find('.badge').exists()).toBe(true)
expect(wrapper.find('.badge').text()).toBe('管理员')
})
it('当 isAdmin 为 false 时不显示管理员徽章', () => {
const wrapper = mount(UserCard, {
props: { name: 'Charlie', isAdmin: false }
})
expect(wrapper.find('.badge').exists()).toBe(false)
})
})
关键点:
mount会渲染组件及其子组件,得到wrapper对象。wrapper.text()获取渲染后的纯文本内容。wrapper.find(selector)查找单个元素,.exists()判断是否存在。- 通过传入不同的 Props,覆盖组件的各种分支状态。
交互测试:模拟用户操作并验证反馈
交互测试验证的是“用户点击按钮后,是否触发了预期的事件或状态变更”。Vue Test Utils 提供了 trigger 方法来模拟 DOM 事件。
// Counter.vue
<template>
<div>
<p data-testid="count">{{ count }}</p>
<button @click="increment">+1</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
const increment = () => { count.value++ }
</script>
// Counter.test.js
import { mount } from '@vue/test-utils'
import Counter from './Counter.vue'
describe('Counter', () => {
it('点击按钮增加计数', async () => {
const wrapper = mount(Counter)
// 初始值为 0
expect(wrapper.get('[data-testid="count"]').text()).toBe('0')
// 模拟点击按钮
await wrapper.find('button').trigger('click')
// 点击后值变为 1
expect(wrapper.get('[data-testid="count"]').text()).toBe('1')
})
it('多次点击累加计数', async () => {
const wrapper = mount(Counter)
const button = wrapper.find('button')
await button.trigger('click')
await button.trigger('click')
await button.trigger('click')
expect(wrapper.get('[data-testid="count"]').text()).toBe('3')
})
})
关键点:
trigger是异步操作,需要使用async/await,因为 Vue 会异步更新视图。- 用
data-testid属性来选取元素,比用 class 或标签更稳定,不易因样式调整而破坏测试。 - 除了点击,还可以模拟输入:
await wrapper.find('input').setValue('hello')。
对于涉及异步请求的场景,可以 Mock 掉 Axios 或使用 Vitest 提供的 vi.fn() 来模拟函数调用,验证结果。
快照测试:防止非预期的 UI 变更
快照测试会记录组件第一次渲染时的 DOM 结构,后续测试会对比当前输出是否与快照一致。如果出现差异,测试会失败,提醒你要么更新了合法的 UI,要么意外改动破坏了原有结构。
// UserCard.test.js 快照测试
import { mount } from '@vue/test-utils'
import { describe, it, expect } from 'vitest'
import UserCard from './UserCard.vue'
describe('UserCard 快照', () => {
it('渲染结果与快照一致', () => {
const wrapper = mount(UserCard, {
props: { name: 'Alice', isAdmin: true }
})
// 生成/对比快照
expect(wrapper.html()).toMatchSnapshot()
})
})
首次运行测试时,Vitest 会在 snapshots 目录下生成一个快照文件,内容类似:
// UserCard.test.js.snap
exports[`UserCard 快照 > 渲染结果与快照一致 1`] = `
"<div class="user-card">
<h3>Alice</h3>
<span class="badge">管理员</span>
</div>"
`;
之后每次运行测试,Vitest 都会将当前输出与这个文件对比。如果你有意修改了 UI(比如增加了 .vip 样式),测试会失败,此时可以选择更新快照:
npx vitest --update
快照测试的注意事项:
- 快照不是“保证正确”,而是“防止意外改动”。不要盲目更新快照,需确认差异是你意图内的变更。
- 避免过于庞大的快照。只对关键组件或小组件做快照测试,不要把一个含几百行 HTML 的页面整体快照,否则任何微小改动都会导致快照失效,失去保护意义。
- 配合其他测试使用。快照测试不能替代行为测试,它只是告诉你“长相没变”,不能验证交互逻辑。
总结:渲染测试确保组件正确显示,交互测试验证行为逻辑,快照测试保护 UI 结构不被意外修改。三者结合,可以覆盖组件开发中绝大多数的质量问题,让重构和协作更有信心。