本系列文已重新編排並新增內容出版成冊,若您喜歡透過書籍來閱讀的話,歡迎至天瓏書局下單選購唷!
今天要來介紹在 Vitest 工具中,有關於測試替身的語法有哪些內容,並且一樣會參考 Gerard Meszaros 的五大測試替身概念,來看看我們應該如何實際應用在真實的測試案例當中!
Vitest 本身作為測試執行環境工具,提供的測試替身(Test Doubles)種類與應用範圍比起 Vue Test Utils 要來的多且廣泛,而在 Vitest 官方文件中對於這一類的測試替身相關的內容泛稱為 「Mocking」;因此,本文將以「Mocking API」 統稱這些 Vitest API,並不代表這些 API 都屬於五大分類中 Mock 類型的測試替身喔!
而 Vitest Mocking API,主要都放置於 vi
模組底下,因此若要使用 Vitest Mocking API 時,我們只要在測試檔案底下引入即可:
import { describe, it, expect, vi} from 'vitest'
若你覺得每個檔案都要這樣引入太麻煩的話,我們可以在 Vitest Config 中設定 test.globals
為 true
,如此一來 Vitest API 將會以全域性的方式提供,我們就不必每次都要在測試檔案中引入 Vitest 了:
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
globals: true,
},
})
倘若專案中有使用到 esLint 工具協助糾錯的話,此時會因為 Vitest API 沒有被引入而顯示宣告上有錯誤,因此我們要在 .eslintrc.json
中主動告知有哪些是屬於全域性的變數:
{
// ...
"globals": {
"describe": true,
"it": true,
"expect": true,
"vi": true
},
// ...
}
若專案中有使用到 TypeScript 的話,則需在 tsconfig.json
中補上 types
,如此一來就能抓到透過全域變數的型別:
{
// ...
"compilerOptions": {
"types": ["vitest/globals"]
}
// ...
}
在設定完這些後,我們就可以來看看 Vitest Mocking API 有哪些方法。
Vitest 本身內建的 Mocking API 主要有以下幾種分類:
setTimeout
、setInterval
)在開發的過程中,有時候我們可能會遇到功能實作與系統時間有關的實作,這時候我們就可以使用這些 API 來協助我們進行測試:
vi.useFakeTimers
vi.setSystemTime
vi.useRealTimers
vi.getMockedSystemTime
vi.getRealSystemTime
vi.restoreCurrentDate
在測試案例中模擬日期時,我們可以透過 vi.useFakeTimers
來模擬系統時間,接著就可以透過 vi.setSystemTime
設定模擬系統時間:
it('should mock system time', () => {
vi.useFakeTimers()
vi.setSystemTime(new Date('2022-10-13'))
expect(new Date()).toEqual(new Date('2022-10-13'))
})
若要恢復模擬系統時間,可以透過 vi.useRealTimers
來恢復系統時間:
it('should restore system time', () => {
vi.useFakeTimers()
vi.setSystemTime(new Date('2022-10-13'))
vi.useRealTimers()
expect(new Date()).not.toEqual(new Date('2022-10-13'))
})
也可以結合先前 Setup & Teardown API 的概念,快速設定各個測試案例:
const formatDateTime = (date) => {
const year = date.getFullYear()
const month = date.getMonth() + 1
const day = date.getDate()
return `${year}-${month}-${day}`
}
describe('mock system time', () => {
beforeEach(() => {
vi.useFakeTimers()
})
afterEach(() => {
vi.useRealTimers()
})
it('should mock system time', () => {
vi.setSystemTime(new Date('2022-10-13'))
expect(new Date()).toEqual(new Date('2022-10-13'))
})
it('should restore system time', () => {
expect(formatDateTime(new Date())).toEqual(formatDateTime(new Date('2022-10-13')))
})
})
若要取得目前模擬的系統時間,可以透過 vi.getMockedSystemTime
來取得:
it('should get mocked system time', () => {
vi.useFakeTimers()
vi.setSystemTime(new Date('2022-10-13'))
expect(vi.getMockedSystemTime()).toEqual(new Date('2022-10-13'))
})
若要取得目前真實系統的時間,則可以透過 vi.getRealSystemTime
來取得:
it('should get real system time', () => {
vi.useFakeTimers()
vi.setSystemTime(new Date('2022-10-13'))
expect(vi.getRealSystemTime()).not.toEqual(new Date('2022-10-13'))
})
最後,想要將從模擬系統時間恢復成真實系統的時間,可以透過 vi.restoreCurrentDate
來恢復:
it('should restore current date', () => {
vi.useFakeTimers()
vi.setSystemTime(new Date('2022-10-13'))
vi.restoreCurrentDate()
expect(new Date()).not.toEqual(new Date('2022-10-13'))
})
Vitest 也提供了與計時器相關的 API,讓我們可以模擬 setTimeout
、setInterval
的行為:
vi.useFakeTimers
vi.runAllTimers
vi.runOnlyPendingTimers
vi.advanceTimersByTime
vi.advanceTimersToNextTimer
vi.restoreAllMocks
與日期相關的 API 類似,在模擬計時器之前,我們要透過 vi.useFakeTimers
讓當下環境中的計時器指定為模擬的計時器,接著就可以使用 runAllTimers
讓模擬計時器開始執行計時的功能:
it('should mock timers', () => {
vi.useFakeTimers()
const mockFn = vi.fn()
setTimeout(mockFn, 1000)
vi.runAllTimers()
expect(mockFn).toHaveBeenCalled()
})
有時候我們計時器可能會有遞迴的情況,這時候可以透過 vi.runOnlyPendingTimers
來執行下一個應該要執行的計時器,從而捕捉各個計時器之間發生過程的相關內容:
it('should run only pending timers', () => {
vi.useFakeTimers()
const cache = {
count: 0,
}
const mockFn = vi.fn(() => {
cache.count++
setTimeout(() => {
mockFn()
}, 1000)
})
mockFn() // 此時 cache.count === 1
expect(cache.count).toEqual(1)
vi.runOnlyPendingTimers() // 執行下一個 setTimeout
expect(cache.count).toEqual(2)
vi.runOnlyPendingTimers() // 執行下一個 setTimeout
expect(cache.count).toEqual(3)
vi.runOnlyPendingTimers() // 執行下一個 setTimeout
expect(cache.count).toEqual(4)
})
除了階段性的執行計時器,若想直接讓計時器被提前到指定的時間,可以透過 vi.advanceTimersByTime
來達成:
it('should advance timers by time', () => {
vi.useFakeTimers()
const mockFn = vi.fn()
setTimeout(mockFn, 1000)
vi.advanceTimersByTime(999) // 計時器被提前 999 毫秒執行
expect(mockFn).not.toHaveBeenCalled()
vi.advanceTimersByTime(1) // 計時器被提前 1000 毫秒執行
expect(mockFn).toHaveBeenCalled()
})
若想直接讓計時器被提前到下一個計時器的時間,可以透過 vi.advanceTimersToNextTimer
來達成:
it('should advance timers to next timer', () => {
vi.useFakeTimers()
const mockFn = vi.fn()
setTimeout(mockFn, 1000)
setTimeout(mockFn, 3000)
vi.advanceTimersToNextTimer() // 所有計時器提前了 1000 毫秒
expect(mockFn).toHaveBeenCalled()
vi.advanceTimersToNextTimer() // 所有計時器被提前了 3000 毫秒
expect(mockFn).toHaveBeenCalled()
})
最後,如果想要將從模擬計時器恢復成真實計時器的時候,可以透過 vi.restoreAllMocks
方法達成:
it('should restore all mocks', () => {
vi.useFakeTimers()
const mockFn = vi.fn()
setTimeout(mockFn, 1000)
vi.restoreAllMocks()
expect(mockFn).not.toHaveBeenCalled()
})
今日 Vitest Mocking API 先介紹到這邊,相信光是模擬日期與計時器的 API,就足夠讓你驚訝於 Vitest Mocking API 的威力,在瀏覽器環境囂張的計時器,在測試環境中,竟然可以如此任由我們所擺佈。
但最重要的是由於我們可以細微的控制日期與計時器的執行時機,因此可以讓我們的測試案例能夠模擬各種不同的狀況,讓我們的測試案例能更加完整與彈性。
而明天我們將會繼續介紹 Vitest Mocking API 的其他功能,學習完這些內容之後,我們就可以開始來寫有關 Vue 周邊工具的測試案例了!