哈嘍,各位小夥伴,歡迎來到我是wangfang呀的博客!我是我是wangfang呀,雖然還在編程的“菜鳥”階段,但我已經迫不及待地想和大家分享我一路上踩過的坑和學到的小技巧。如果你也曾為bug頭疼,那麼你來對地方了!今天的內容希望能夠給大家帶來一些靈感和幫助。
前言
一句話先立旗: 寫測試 ≠ 浪費時間,寫測試 = 把 BUG 容易出現的地方提前炸出來。 這一篇圍繞 四大關鍵詞 —— 測試框架選型、vue-test-utils、組件單測套路、Mock/Spy/快照/覆蓋率 — 帶你把 Vue 3 項目測試跑通、跑快、跑穩。
1. 測試框架選型:Jest vs Mocha vs Vitest
| 維度 | Jest | Mocha + Chai | Vitest |
|---|---|---|---|
| 維護方 | Meta | 社區 | Vite 核心團隊 |
| 斷言 / Mock | 內置 | 需 Chai / Sinon | 內置,Jest API 對齊 |
| 運行速度 | 基於 JSDOM,Cold start 較慢 | 快,配置靈活 | 極快(esbuild + 原生 ESM),HMR 體驗 |
| TS 支持 | ts-jest / babel-jest | ts-node/register |
內置 esbuild 編譯 |
| Vue 官方 Demo | 早期 CLI 默認 | 早期選項 | Vite 模板默認 |
| 適用場景 | 老項目 / 大型生態依賴 | 極度自定義 | Vue3 + Vite 項目首選 |
結論
- 全新 Vite + Vue3:直接上 Vitest(幾乎 100% 兼容 Jest API)
- 舊 CLI(webpack)或依賴 Jest 插件生態:繼續 Jest
- 需要極高級自定義、非 Node 環境:Mocha 自由度最高
2. @vue/test-utils 基本用法
2.1 安裝
pnpm add -D @vue/test-utils vitest # 示例以 Vitest 為主
2.2 核心 API 速查表
| API | 作用 |
|---|---|
mount() |
完整渲染組件(包含子組件) |
shallowMount() |
淺渲染 —— 子組件自動 stub |
wrapper.find(selector) |
查詢元素 / 子組件 |
wrapper.get() |
與 find 同,但未找到會拋錯 |
wrapper.setProps() |
更新 props(異步) |
wrapper.setValue() |
更改 <input> 值並觸發 input 事件 |
wrapper.emitted() |
獲取組件 $emit 記錄 |
flushPromises() |
等待異步(Promise 微任務) |
import { mount } from '@vue/test-utils'
import Counter from '@/components/Counter.vue'
test('按鈕點擊後計數 +1', async () => {
const wrapper = mount(Counter)
expect(wrapper.text()).toContain('0')
await wrapper.find('button').trigger('click')
expect(wrapper.text()).toContain('1')
})
3. 組件單元測試套路
3.1 場景拆解
| 測什麼 | 關注點 |
|---|---|
| 渲染 | DOM 是否按 props / slots 呈現 |
| 交互 | 點擊、輸入後 state 變化、事件觸發 |
| 業務邏輯 | 計算屬性、watcher、方法輸出 |
| 副作用 | 發請求、存儲、路由跳轉等(需 Mock) |
3.2 示例組件 TodoItem.vue
<template>
<li :class="{ done: item.done }">
<input type="checkbox" v-model="checked" />
<span>{{ item.text }}</span>
</li>
</template>
<script setup>
const props = defineProps<{ item: { id: number; text: string; done: boolean } }>()
const emit = defineEmits(['toggle'])
const checked = useVModel(props, 'item.done') // 假設封裝
watch(checked, val => emit('toggle', props.item.id, val))
</script>
3.3 單測示例
import { mount } from '@vue/test-utils'
import TodoItem from '@/components/TodoItem.vue'
const factory = (overrides = {}) =>
mount(TodoItem, {
props: {
item: { id: 1, text: 'Play', done: false },
...overrides
}
})
test('初始渲染', () => {
const w = factory()
expect(w.text()).toContain('Play')
expect(w.classes()).not.toContain('done')
})
test('勾選後觸發 toggle 事件', async () => {
const w = factory()
await w.find('input').setValue(true)
expect(w.emitted('toggle')![0]).toEqual([1, true])
expect(w.classes()).toContain('done')
})
4. Mock / Spy / 快照 / 覆蓋率
4.1 Mock 函數 & 模塊
import { vi } from 'vitest'
vi.mock('@/api', () => ({
fetchUser: vi.fn(() => Promise.resolve({ name: 'Alice' }))
}))
- 與 Jest 的
jest.mock等價 - 可在測試用例內部動態修改返回值
4.2 Spy 監聽
import { fetchUser } from '@/api'
test('調用次數', async () => {
const spy = vi.spyOn(api, 'fetchUser')
await getUserProfile()
expect(spy).toHaveBeenCalledTimes(1)
})
4.3 快照測試
const wrapper = mount(Navbar)
expect(wrapper.html()).toMatchSnapshot()
- 首次執行會生成
.snap文件 - 結構大改需
--update更新快照 - 謹慎使用:佈局頻繁改動的組件會導致快照紅炸
4.4 覆蓋率
vitest run --coverage
# 或 jest --coverage
生成 coverage/ 報告(html / text)。
- 關注 行覆蓋(Line) + 分支覆蓋(Branch)
- 不必追求 100%,但核心業務 / 邊界分支最好全覆蓋
⚠️ 實戰踩坑 & 性能提示
| 問題 | 解決 |
|---|---|
| 異步更新斷言失敗 | await nextTick() 或 flushPromises() 再斷言 |
| Vitest 與 JSDOM API 不兼容 | testEnvironment: 'jsdom' 已內置;若需元素尺寸,用 vi.stubGlobal('getComputedStyle', …) |
| Vue Router/Pinia 注入 | global.plugins: [router, pinia] 或 wrapper = mount(App, { global: { plugins: […] } }) |
| 大倉庫測試慢 | 按需 --run 指定 pattern、開啓 threads:false 調單核調試 |
| 快照忽略動態 ID | expect(html).toMatchSnapshot({ ignore: [/id="[^"]*/] }) |
🚀 結語
- 框架選型:新 ◀︎ Vitest;老 ◀︎ Jest
- 測試金字塔:單元(多) → 組件集成 → E2E(少)
- vue-test-utils:
mount / find / trigger / emitted四件套最常用 - Mock & Spy:隔絕網絡和副作用,讓測試純粹、可復現
- 覆蓋率:用來發現盲區,不是 KPI 的數字比賽
“測試寫得好,需求改得早”。—— 當需求來回變而你依舊底氣十足,就是測試真正的價值所在!
好啦,今天的內容就先到這裏!如果覺得我的分享對你有幫助,給我點個贊,順便評論吐個槽,當然不要忘了三連哦!感謝大家的支持,記得常回來,我是wangfang呀等着你們的下一次訪問!