随着 Vue 3 的普及,前端单元测试的重要性日益凸显。一个可靠的测试套件不仅能保证代码质量,还能显著提升重构的信心。在 Vue 3 生态中,Vitest 与 Vue Test Utils 的组合已经成为事实上的标准测试方案。本文将带你从零开始,掌握使用 Vitest 和 Vue Test Utils 编写可靠单元测试的完整流程。
为什么选择 Vitest 和 Vue Test Utils
Vitest 是由 Vite 团队开发的测试运行器,它与 Vite 共享配置,启动速度极快,支持 TypeScript、JSX 和 ESM。相比 Jest,Vitest 在 Vue 3 项目中拥有更自然的集成体验。Vue Test Utils 则是 Vue 官方提供的组件测试工具库,它提供了挂载组件、模拟用户交互、访问组件实例等核心能力。
两者结合,可以让你在几乎零配置的情况下,获得热更新测试、快照测试、覆盖率报告等完整功能。
环境搭建与基础配置
首先,在 Vue 3 项目中安装必要的依赖:
npm install -D vitest @vue/test-utils jsdom @vitest/coverage-v8
接着,在 vite.config.ts 中添加测试配置:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
test: {
environment: 'jsdom',
globals: true,
coverage: {
provider: 'v8',
reporter: ['text', 'html']
}
}
})
在 package.json 中添加测试脚本:
{
"scripts": {
"test": "vitest",
"test:run": "vitest run",
"test:coverage": "vitest run --coverage"
}
}
至此,环境搭建完成。运行 npm run test 即可启动监听模式。
编写第一个组件测试
假设我们有一个简单的计数器组件 Counter.vue:
<template>
<div>
<span data-test="count">{{ count }}</span>
<button data-test="increment" @click="increment">+1</button>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const count = ref(0)
const increment = () => count.value++
</script>
对应的测试文件 Counter.spec.ts:
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import Counter from './Counter.vue'
describe('Counter.vue', () => {
it('初始计数为 0', () => {
const wrapper = mount(Counter)
expect(wrapper.get('[data-test="count"]').text()).toBe('0')
})
it('点击按钮后计数增加', async () => {
const wrapper = mount(Counter)
await wrapper.get('[data-test="increment"]').trigger('click')
expect(wrapper.get('[data-test="count"]').text()).toBe('1')
})
})
这里有几个关键点值得注意:
- 使用
data-test属性作为选择器,避免依赖样式类名。 - 触发事件后必须
await,确保 Vue 的响应式更新完成。 - 使用
wrapper.get()而非find(),前者在元素不存在时会直接报错,让测试更可靠。
测试 Props、Emit 与插槽
组件测试的核心在于验证输入输出。对于 Props,可以直接传入并断言渲染结果;对于 Emit,可以通过 wrapper.emitted() 检查事件是否触发。
it('接收 props 并正确渲染', () => {
const wrapper = mount(Greeting, {
props: { name: 'Vue' }
})
expect(wrapper.text()).toContain('Hello, Vue')
})
it('点击时触发 submit 事件', async () => {
const wrapper = mount(Form)
await wrapper.get('button').trigger('click')
expect(wrapper.emitted('submit')).toHaveLength(1)
expect(wrapper.emitted('submit')![0]).toEqual([{ name: 'test' }])
})
测试插槽时,可以使用 slots 选项:
const wrapper = mount(Card, {
slots: {
default: '<p>内容</p>',
header: '<h1>标题</h1>'
}
})
expect(wrapper.find('h1').text()).toBe('标题')
模拟依赖:Mock 与 Stub
真实组件往往依赖外部模块(如 API 请求、路由、Pinia store)。Vitest 提供了强大的 vi.mock 能力。
import { vi } from 'vitest'
import { fetchUser } from '@/api/user'
vi.mock('@/api/user', () => ({
fetchUser: vi.fn().mockResolvedValue({ name: 'Alice' })
}))
it('加载用户数据后渲染姓名', async () => {
const wrapper = mount(UserProfile)
await flushPromises()
expect(wrapper.text()).toContain('Alice')
})
对于子组件,可以使用 global.stubs 进行替换,避免测试被无关逻辑干扰:
const wrapper = mount(Parent, {
global: {
stubs: {
ChildComponent: true
}
}
})
测试组合式函数
Vue 3 的组合式函数(Composables)是逻辑复用的核心。测试它们无需挂载组件,直接调用即可:
import { useCounter } from './useCounter'
it('useCounter 可以递增', () => {
const { count, increment } = useCounter()
expect(count.value).toBe(0)
increment()
expect(count.value).toBe(1)
})
如果组合式函数依赖生命周期钩子,可以将其包裹在一个测试组件中挂载,或使用 withSetup 辅助函数。
测试异步逻辑与路由
异步操作是测试中的常见痛点。使用 flushPromises 可以等待所有 Promise 完成:
import { flushPromises } from '@vue/test-utils'
it('异步加载后显示数据', async () => {
const wrapper = mount(AsyncList)
await flushPromises()
expect(wrapper.findAll('li')).toHaveLength(3)
})
对于使用 Vue Router 的组件,可以创建真实的路由实例并挂载:
import { createRouter, createMemoryHistory } from 'vue-router'
const router = createRouter({
history: createMemoryHistory(),
routes: [{ path: '/', component: Home }]
})
const wrapper = mount(App, {
global: { plugins: [router] }
})
await router.isReady()
测试覆盖率与最佳实践
运行 npm run test:coverage 可以生成覆盖率报告。但覆盖率不是目的,而是手段。以下是一些经过验证的最佳实践:
- 测试行为而非实现:关注组件对外表现,而非内部状态。
- 一个测试一个断言重点:让失败信息更清晰。
- 避免过度 mock:mock 越多,测试与真实行为的偏差越大。
- 使用
data-test选择器:与样式解耦,重构更安全。 - 保持测试快速:Vitest 的优势在于速度,避免在单元测试中做端到端的事。
总结
Vitest 与 Vue Test Utils 的组合为 Vue 3 项目提供了现代化、高性能的测试体验。从环境搭建到组件测试、依赖模拟、组合式函数测试,本文覆盖了日常开发中的核心场景。掌握这些技能后,你将能够编写出可靠、可维护的单元测试,为项目的长期演进打下坚实基础。
测试不是负担,而是对代码质量的持续投资。现在就开始为你的 Vue 3 项目添加第一个测试吧。
未经允许不得转载:任鹏个人博客 » Vue 3 测试指南:使用 Vitest 与 Vue Test Utils 编写可靠的单元测试


朋友圈点赞图在线生成源码