在 Day 8,我們打造了第一個可複用的 BaseButton 元件。
我們在 LoginView.vue
中將它展示出來,手動點擊,看起來一切正常。但這就夠了嗎?
想像一下,未來我們為了某個新需求,修改了 BaseButton 的程式碼。
如果只靠手動測試,我們必須在每次修改後,把所有用到 BaseButton
的地方都點擊一遍,這非常耗時且容易出錯。
自動化測試就是為了解決這個問題:我們用程式碼來驗證程式碼的行為,建立一張「安全網」,確保元件的每一次修改都不會破壞既有功能。這就是所謂的「迴歸測試 (Regression Testing)」。
今天,我們將學習如何使用 Vue 生態系中最主流的測試工具 Vitest,為我們的 BaseButton
建立這張至關重要的安全網。
在 Vue 專案中,我們通常需要兩個核心工具來進行元件測試:
@testing-library/vue
): 一個輔助測試的工具庫。它讓我們能用「使用者視角」來測試元件,而不是去鑽研元件內部的實作細節。它就像一位「模擬使用者」,會實際去渲染元件、找按鈕、點擊它,並回報互動後的結果。如果我們的專案在建立時沒有選擇安裝測試工具,可以透過以下指令手動安裝:
// 安裝 Vitest, JSDOM (模擬瀏覽器環境) 和 Vue Testing Library
npm install -D vitest jsdom @testing-library/vue
(註:-D
代表只在開發環境下安裝,因為測試程式碼不會被打包到最終的產品中)
我們測試的第一步,是確保元件能被成功渲染到畫面上。
按照慣例,我們的測試檔案會與元件檔案放在一起,並以 .spec.js 或 .test.js 結尾。
讓我們先建立src/components/BaseButton.spec.js。
一個測試檔案的基本結構包含 describe (描述一個測試群組) 和 it (或 test,描述一個獨立的測試案例)。
// 從 vitest 引入測試需要的基本函式
import { describe, it, expect } from 'vitest'
// 從 @testing-library/vue 引入測試工具
import { render, screen, fireEvent } from '@testing-library/vue'
// 引入我們要測試的元件
import BaseButton from './BaseButton.vue'
// 開始描述一組關於 BaseButton 的測試
describe('BaseButton', () => {
// case1:它應該要能被渲染
it('should render properly', () => {
// 1. 渲染元件
render(BaseButton, {
slots: {
default: 'Click Me'
}
})
// 2. 透過 "使用者視角" 的方式尋找畫面上的元素
// screen.getByText 會尋找畫面上包含 'Click Me' 文字的元素
const button = screen.getByText('Click Me')
// 3. 斷言 (Assert) - 預期這個元素確實存在於畫面上
expect(button).toBeTruthy()
})
這段測試的白話文就是:「我描述一個關於 BaseButton
的測試,其中一個案例是『它應該要能被正常渲染』。我動手渲染它,並傳入『ClickMe』的文字,然後我預期畫面上真的能找到『Click Me』這個按鈕。」
光是能渲染還不夠,我們需要驗證傳遞 props 是否能正確改變元件的行為和外觀。
// ... 接續上方程式碼
// case2: variant prop
it('should apply correct classes for variant prop', () => {
render(BaseButton, {
props: {
variant: 'secondary' // 傳入 variant prop
}
})
// 透過 role 來尋找按鈕 (這是更語意化的方式)
const button = screen.getByRole('button')
// 斷言:預期按鈕身上有 'btn-secondary' 這個 class
expect(button.classList.contains('btn-secondary')).toBe(true)
})
// case3: disabled prop
it('should be disabled when disabled prop is true', () => {
render(BaseButton, {
props: {
disabled: true // 傳入 disabled prop
}
})
const button = screen.getByRole('button')
// 斷言:預期按鈕處於被禁用的狀態
expect(button.disabled).toBe(true)
})
這是最重要的一環:驗證使用者的互動是否能觸發我們預期的 emit 事件。
// ... 接續上方程式碼
// case4: 測試點擊事件
it('should emit a click event when clicked', async () => {
// render 函式會回傳一個包含 emitted 方法的物件
const { emitted } = render(BaseButton)
const button = screen.getByRole('button')
// 模擬使用者點擊按鈕
await fireEvent.click(button)
// 斷言:預期 'click' 事件被發送了
// emitted() 會回傳一個物件,key 是事件名稱
expect(emitted().click).toBeDefined()
// 驗證事件被發送了幾次
expect(emitted().click.length).toBe(1)
})
// case5: disabled 狀態下不會發射事件
it('should NOT emit a click event when disabled', async () => {
const { emitted } = render(BaseButton, {
props: { disabled: true }
})
const button = screen.getByRole('button')
await fireEvent.click(button)
// 斷言:預期 'click' 事件「沒有」被發送
expect(emitted().click).toBeUndefined()
})
})
執行測試前可以先在 package.json
中定義 scripts 的測試指令:
"scripts": {
"test": "vitest",
"test:run": "vitest run",
"test:ui": "vitest --ui"
}
如果沒有設定的話,預設指令為 npx vitest run
。
接著只要執行:
npm run test
Vitest 會啟動,並回報所有測試案例的執行結果。當你看到滿滿的綠色通過標記時,代表你很棒,繼續保持。
今天,我們為 BaseButton
做了元件的自動化測試。我們學會了:
有了測試的保護,未來我們在重構、擴充功能時,只要測試案例依然通過,我們的元件就沒有問題。
明天,我們將回到應用的功能面,Day 10:[Componentの呼吸・參之型] Input打造 - 表單輸入元件開發。
心を燃やせ 🔥!