從 Vitest 開始:Vue 3 元件單元測試實戰指南

各位寫 Vue 3 的朋友,有沒有過這種經驗:改了一個小功能,結果另一個頁面莫名其妙壞掉?或者重構時手在發抖,深怕動了哪裡就炸?這時候「單元測試」就是你的安全網。今天我們用 Vitest(Vite 官方推薦的測試框架)搭配 @vue/test-utils,從零把 Vue 3 專案的測試跑起來。

一、為什麼選 Vitest

  • 啟動極快:與 Vite 共用設定與轉譯管線,不必另起一個 babel/jest 環境。
  • 原生支援:ESM、TypeScript、JSX 通通開箱即用,不用額外配置。
  • API 相容 Jest:describe / it / expect 幾乎一致,從 Jest 遷移成本極低。

重點:若你本來就用 Vite 開發,Vitest 幾乎是零摩擦的選擇,不需要為了測試再養一套工具鏈。

二、安裝與設定

步驟 1:安裝開發依賴。

npm i -D vitest @vue/test-utils jsdom

步驟 2:在 vite.config.ts 加上 test 設定區塊。

import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  test: {
    environment: 'jsdom',
    globals: true,
  },
})

步驟 3:在 package.json 加上腳本,方便日常執行。

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

三、寫第一個元件測試

以一個計數器元件 Counter.vue 為例。

<template>
  <button @click="count++">{{ count }}</button>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>

對應的測試檔 Counter.test.ts 如下。

import { mount } from '@vue/test-utils'
import { describe, it, expect } from 'vitest'
import Counter from './Counter.vue'

describe('Counter', () => {
  it('點擊按鈕後數字加 1', async () => {
    const wrapper = mount(Counter)
    expect(wrapper.text()).toContain('0')
    await wrapper.find('button').trigger('click')
    expect(wrapper.text()).toContain('1')
  })
})

重點:mount 會把元件渲染成可互動的 DOM,trigger 模擬使用者事件;非同步的操作要記得 await。

四、測試 Pinia store

實戰中我們常用 Pinia 管理狀態,測試時要用 createPinia 注入一個乾淨的環境。

import { setActivePinia, createPinia } from 'pinia'
import { useCartStore } from './cart'

beforeEach(() => setActivePinia(createPinia()))

it('加入商品後總額更新', () => {
  const cart = useCartStore()
  cart.add('apple', 30)
  expect(cart.total).toBe(30)
})

每個測試前重置 Pinia,才能保證用例之間互不污染。

五、常見陷阱與最佳實踐

  • 測行為而非實作:關注使用者看到的結果(文字、事件),不要去測試內部變數名稱。
  • 狀態要隔離:beforeEach 裡重置 Pinia / 元件狀態,避免上一個用例影響下一個。
  • 非同步要等:trigger、nextTick、await 的 Promise 都要確實等待完成。

特別注意:globals: true 才不用每個檔案手動 import describe/it/expect;若設為 false,記得每個測試檔都要自行 import。

互動話題:你在專案裡有寫測試的習慣嗎?最常被測試卡住的環節是什麼?歡迎留言分享,我們一起把「不敢改」變成「隨便改」。