Vitest 单测使用

在 Vue 3 + Vite 生态中,Vitest 是绝对的首选,因为它与 Vite 共享配置,速度极快,且 API 与 Jest 高度兼容。

下面是 Vitest 的完整接入指南,涵盖了从「环境配置」到「实战测试(含 Vue 组件测试)」的全流程。


一、 核心配置步骤

1. 安装核心依赖

除了测试框架本身,还需要安装 Vue 组件测试工具和 JSDOM(提供浏览器环境):

pnpmadd-Dvitest @vue/test-utils jsdom
2. 配置vite.config.ts

在现有的 Vite 配置中追加test字段:

// vite.config.tsimport{defineConfig}from'vite'importvuefrom'@vitejs/plugin-vue'exportdefaultdefineConfig({plugins:[vue()],test:{environment:'jsdom',// 模拟浏览器 DOM 环境globals:true,// 全局注入 describe, it, expect 等,无需手动 import// 自动清理 mock 状态,避免测试用例间相互污染clearMocks:true,},})
3. 补充 TS 类型提示

tsconfig.app.json中加入vitest/globals,让编辑器认识describeexpect

{"compilerOptions":{"types":["vitest/globals"]}}
4. 添加测试脚本

package.json中配置运行命令:

{"scripts":{"test":"vitest","test:run":"vitest run"}}

二、 完整实战示例

示例 1:测试普通工具函数(纯逻辑)

假设我们有一个简单的格式化函数:

// src/utils/format.tsexportfunctionformatPrice(price:number):string{return`¥${price.toFixed(2)}`}

对应的测试文件(建议放在同级目录或__tests__目录下):

// src/utils/__tests__/format.test.tsimport{formatPrice}from'../format'describe('formatPrice',()=>{it('应该正确格式化价格',()=>{expect(formatPrice(100)).toBe('¥100.00')expect(formatPrice(9.9)).toBe('¥9.90')})})
示例 2:测试 Vue 组件(结合 @vue/test-utils)

假设我们有一个简单的按钮组件:

<!-- src/components/BaseButton.vue --> <script setup lang="ts"> const emit = defineEmits(['click']) </script> <template> <button class="base-btn" @click="emit('click')"> <slot /> </button> </template>

组件的测试用例:

// src/components/__tests__/BaseButton.test.tsimport{mount}from'@vue/test-utils'importBaseButtonfrom'../BaseButton.vue'describe('BaseButton',()=>{it('应该正确渲染插槽内容',()=>{constwrapper=mount(BaseButton,{slots:{default:'点击我'}})expect(wrapper.text()).toContain('点击我')})it('点击时应该触发 click 事件',async()=>{constwrapper=mount(BaseButton)awaitwrapper.trigger('click')expect(wrapper.emitted('click')).toHaveLength(1)})})
示例 3:测试 Pinia Store(结合权限路由场景)

测试我们之前写的usePermissionStore

// src/stores/__tests__/permission.test.tsimport{setActivePinia,createPinia}from'pinia'import{usePermissionStore}from'../modules/permission'describe('Permission Store',()=>{beforeEach(()=>{setActivePinia(createPinia())})it('admin 角色应该能看到所有路由',()=>{conststore=usePermissionStore()constroutes=store.generateRoutes(['admin'])// 验证 admin 能获取到包含 system 的路由expect(routes.some(r=>r.path==='/system')).toBe(true)})it('普通用户应该被过滤掉无权限路由',()=>{conststore=usePermissionStore()constroutes=store.generateRoutes(['user'])expect(routes.some(r=>r.path==='/system')).toBe(false)})})

三、 结合 Husky 的自动化流程

为了让测试真正发挥“守住代码质量”的作用,我们将测试加入 Git 提交拦截中:

pnpmadd-Dlint-staged

修改package.json

{"lint-staged":{"*.{ts,vue}":["vitest related --run","eslint --fix","prettier --write"]}}

💡 亮点vitest related是 Vitest 的神级功能!它只会运行与你本次修改的文件有关联的测试,而不是全量跑测试,这样在git commit时速度会非常快。


四、 避坑与最佳实践

  1. 测试文件命名:推荐统一使用xxx.test.tsxxx.spec.ts,Vitest 默认会匹配这些后缀。
  2. 异步测试:如果测试涉及接口请求或定时器,记得使用async/await,或者在it回调中返回 Promise。
  3. Mock 外部依赖:在测试组件时,如果组件内部调用了axiosrouter,使用vi.mock()进行模拟,避免测试变成真实的网络请求:
  4. UI 可视化界面:开发时可以直接运行pnpm test,Vitest 会启动一个 HMR 模式的终端,修改代码后测试会自动重跑,体验极佳。

🎉 完整工程化闭环总结

Vue 3 + Vite 项目已经具备了企业级的完整骨架:

  1. 基础架构:Vite + Vue 3 + TS + Pinia + Vue Router
  2. 工程规范:ESLint + Prettier + Husky + lint-staged
  3. 性能与体验:Element Plus 按需引入 + 动态权限路由
  4. 质量保障:Vitest 单测 + 自动化关联测试

你可以直接基于这套底座开始开发业务了