Vue 3 测试指南:使用 Vitest 与 Vue Test Utils 编写可靠的单元测试

随着 Vue 3 的普及,前端单元测试的重要性日益凸显。一个可靠的测试套件不仅能保证代码质量,还能显著提升重构的信心。在 Vue 3 生态中,VitestVue 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 编写可靠的单元测试

赞 (0) 打赏

评论 0

取消
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏