iT邦幫忙

2025 iThome 鐵人賽

DAY 9
0
Vue.js

打造銷售系統30天修練 - 全集中・Vue之呼吸系列 第 9

Day 9:[Componentの呼吸・貳之型] 測試Button - 學習Component Testing

  • 分享至 

  • xImage
  •  

在 Day 8,我們打造了第一個可複用的 BaseButton 元件。
我們在 LoginView.vue 中將它展示出來,手動點擊,看起來一切正常。但這就夠了嗎?

想像一下,未來我們為了某個新需求,修改了 BaseButton 的程式碼。

  • 他會不會不小心讓 disabled 屬性失效了?
  • 他會不會改壞了 danger 樣式的 CSS class?
  • 他會不會讓 click 事件不再被正確發送?

如果只靠手動測試,我們必須在每次修改後,把所有用到 BaseButton的地方都點擊一遍,這非常耗時且容易出錯。
自動化測試就是為了解決這個問題:我們用程式碼來驗證程式碼的行為,建立一張「安全網」,確保元件的每一次修改都不會破壞既有功能。這就是所謂的「迴歸測試 (Regression Testing)」。

撰寫自動化測試

今天,我們將學習如何使用 Vue 生態系中最主流的測試工具 Vitest,為我們的 BaseButton 建立這張至關重要的安全網。

準備我們的測試環境

在 Vue 專案中,我們通常需要兩個核心工具來進行元件測試:

  1. Vitest: 一個由 Vite 團隊打造的測試執行器 (Test Runner)。它與 Vite 無縫整合,並且語法Jest 框架相容。負責執行測試並回報結果
  2. Vue Testing Library (@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。

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 - 元件的客製化能力

光是能渲染還不夠,我們需要驗證傳遞 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)
    })
 

測試 Events - 元件的互動回饋

這是最重要的一環:驗證使用者的互動是否能觸發我們預期的 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 做了元件的自動化測試。我們學會了:

  1. 為何需要測試:為了建立安全網,防止未來的修改破壞現有功能
  2. 測試的工具:使用 Vitest 作為測試工具,Vue Testing Library 模擬使用者行為。
  3. 如何寫測試:涵蓋了元件的渲染、Props、Slots 和 Events 四個面向的測試方法。

有了測試的保護,未來我們在重構、擴充功能時,只要測試案例依然通過,我們的元件就沒有問題。

明天,我們將回到應用的功能面,Day 10:[Componentの呼吸・參之型] Input打造 - 表單輸入元件開發。
心を燃やせ 🔥!


上一篇
Day 8:[Componentの呼吸・壹之型] Button工坊 - 從零打造Button元件
系列文
打造銷售系統30天修練 - 全集中・Vue之呼吸9
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言