測試框架千千萬,我偏愛 vitest
前端測試框架千千萬,jest、vitest、Jasmine、mocha等等,但我個人更偏愛 vitest,簡潔,高效,現代化,這篇文章就帶大家了解一下關于vitest的一些知識和特性,幫助大家更好的使用vitest。
why vitest
Vitest 由核心Vite團隊成員Anthony fu(一個托尼)開發,它建立在Vite之上,但非vite項目其實也可以使用vitest。而vite想必大家都非常熟悉了,它可以快速的啟動開發服務器,利用瀏覽器的ES模塊系統,從而加快本地開發速度,這讓vite變得十分受歡迎。
雖然用vite開發的項目也可以和jest一起使用,但是vite和jest中其實有很多配置重復的部分,迫使用戶要維護和運行兩套流程。
所以,vitest一開始要解決的問題就是便于在vite項目中方便集成測試能力;在開發中,將Vitest與Vite結合使用的主要優勢是他們都有很好的性能,方便一起集成,以及他們都共享配置同一份配置。
vite的爆火一定程度上也讓vitest增加了曝光度,它也在社區中十分受歡迎,成為了越來越多測試框架的首選:
Vitest 對比 jest
說到vitest,不得不提另外一個測試框架也就是jest。
早在2011年,還沒有JavaScript測試框架能滿足Facebook(meta)團隊的測試需求,所以他們創建了Jest。Jest在2014年開源,就在React開源后不久,jest也隨著React迅速流行起來。2016年,jest成為create react APP(CRA)命令創建react應用的首選測試框架,Next.js V12版本也把jest作為默認測試框架,jest主流地位已然確立。
2022年,jest所有權已經轉移給OpenJS基金會,由meta外部人員維護,之后jest的更新和維護就變得相對緩慢。jest的最新一次更新版本在1年前,jest的v30版本早在2023年就已經發布alpha版本,但是現在已經2025年了,v30版本仍然沒有發布;但是vitest去年就從v1版本更新到了v3版本......
vitest對比jest一個顯著的優勢是vitest原生支持typescript、ESM,而jest v29版本現在對于ESM的支持還是實驗性的,此外如果要支持typescript還需要配置babel,通過babel來轉譯支持typescript。而vitest對于現代特性都是開箱即用的。
關于性能方面,在redit論壇 (https://www.reddit.com/r/reactjs/comments/10zyse3/is_jest_still_faster_than_vitest/)上有很多關于jest和vitest的性能測試的討論,有的反饋說jest比較快,有的說vitest比較快。但不管怎么樣,vitest的HMR特性是要比jest是要快的,也就是開發過程中只運行修改文件的測試。原因是vitest是基于修改模塊的依賴樹去做的分析重新運行,而jest是基于git未提交代碼做的分析,這可能不太準確,因為并非所有檢測到的更改文件都與現在運行的測試相關。
下面是關于jest和vitest特性的一個全面對比:
vitest有更多的現代特性,以及vitest處于一個快速迭代社區逐漸繁榮的階段,如果是一個使用現代特性的新項目,那么vitest肯定是首選!
vitest的一些特性
關于怎么開始和配置,比較簡單,本文就不展開了,下面詳細聊聊vitest的一些特性,了解完這些特性后,想必你對vitest有更全面的認識。
兼容jest API 但是更強
因為jest的API已經設計的比較優秀(從他的下載占有率可以看出),vitest幾乎完全兼容了jest的API設置。比如Expect、Mock、Setup and TearDown等API。所以從jest項目遷移到vitest只需要做很少的調整,具體可以參考官方文檔(https://vitest.dev/guide/migration.html#jest),其中一個都會遇到的調整是globals的配置,他可以不用引入vitest,也能使用全局API。
此外,vitest的v3 API增加了更多的能力,比如toHaveBeenCalledExactlyOnceWith,他可以檢測某個函數只被調用了一次且調用參數為預期值。
test('vitest mocking example', () => {
const mock = vi.fn();
mock('hello');
// New matcher for more precise assertions
expect(mock).toHaveBeenCalledExactlyOnceWith('hello');
// Spy reuse example
const obj = {
method: () => {}
};
const spy = vi.spyOn(obj, 'method');
// Spy is automatically reused if spyOn is called again
const sameSpy = vi.spyOn(obj, 'method');
expect(spy === sameSpy).toBe(true);
});
默認watch模式
在jest中,我們要使用watch模式的時候,需要增加--watch參數(即jest --watch);但是vitest默認使用watch模式(即vitest),不需要增加--watch參數,也能自動進入watch模式。
但是如果是CI環境,我們運行watch模式,是不是不符合預期?
vitest會根據是否有環境變量process.env.CI判斷是否處于CI環境,如果有環境變量則不會啟動watch模式。
當然也可以通過指定--watch參數以及--run參數,來指定watch模式還是單次運行。
源碼內聯測試
什么是源碼內聯測試(In-Source Testing)呢?
這是一個rust模塊測試的方式,他可以讓我們把測試代碼寫在源碼中。
這可以讓測試代碼和源碼代碼使用相同的閉包環境,關注的邏輯和代碼在同一地方,這可以讓我們更快速的發現問題、修復問題,每次代碼修改后都能立即運行測試代碼并進行修復,便于維護;但是由于測試代碼和源代碼在一個文件里,非常容易耦合,所以對代碼質量有一定要求。
下面是一個內聯代碼測試的demo:
export function add(...args: number[]) {
return args.reduce((a, b) => a + b, 0)
}
// in-source test suites
if (import.meta.vitest) {
const { it, expect } = import.meta.vitest
it('add', () => {
expect(add()).toBe(0)
expect(add(1)).toBe(1)
expect(add(1, 2, 3)).toBe(6)
})
}
官方建議對更復雜的測試使用單獨的測試文件,源代碼內測試適用于對小函數進行單元測試和原型設計,因此對于為 JavaScript 庫編寫測試特別有用。
此外,如果我們在生產環境使用該模式,應該為bundler增加變量定義,以便于bundler分析dead code并刪除:
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
includeSource: ['src/**/*.{js,ts}'],
},
define: {
'import.meta.vitest': 'undefined',
},
})
真正的瀏覽器測試
在vitest v2版本引入了瀏覽器模式,該模式還處于實驗階段。該模式將在瀏覽器環境直接運行代碼,所以他可以訪問真正的dom API,所以理論上在實際環境中運行的測試將會更加可靠。缺點就是慢!
在node環境的測試中,我們一般通過js-dom或者happy-dom來模擬瀏覽器環境,happy-dom支持更少的瀏覽器API,但是更快,js-dom則相反。我們可以根據需要選擇不同的dom模擬環境,vitest同時支持js-dom和happy-dom。
vitest支持在不同的容器運行測試,比如playwright或者webdriverio ,這兩者比較顯著的差異是他們底層協議不同,playwright使用的Chrome DevTools 協議,而webdriverio使用的是WebDriver 協議:
vitest提供了開箱即用的包來渲染常用的前端框架的組件,下面是一個react的demo:
import { expect, test } from'vitest'
import { render } from'vitest-browser-react'
import HelloWorld from'../src/HelloWorld'
test('renders name', async () => {
const { getByText, getByRole } = render(<HelloWorld name="Vitest" />)
await expect.element(getByText('Hello Vitest x1!')).toBeInTheDocument()
await getByRole('button', { name: 'Increment '}).click()
await expect.element(getByText('Hello Vitest x2!')).toBeInTheDocument()
})
可以看到API和用法幾乎和@testing-library/react幾乎相同,相同的代碼可以讓運行環境切換到瀏覽器環境!
其他的特性
- Browber UI。直接啟動一個前端頁面,在這個頁面中可以更好的調試、運行、修復測試;
- 類型測試。提供類型斷言的API可以對TS的類型進行測試;
- 提供benchmark
- workspace配置。v3版本的vitest增加了workspace配置,它特別適用于monorepo,可以在一個配置文件里配置多個項目的配置。
- ...
這里就不一一列舉了,感興趣的小伙伴建議直接查看官網。
一些技巧
下面分享個人在使用vitest的過程中的一些有用的技巧。
使用vscode的debug terminal
這是一個node調試比較通用的技巧,對于vitest同樣適用。使用vscode 的debug terminal可以非常方便的查看變量的參數,這非常適合我們在mock出現問題的時候debug。
使用skip或者only來快速調試
在調試單元測試的時候,我們經常會遇到單測報錯,但是可能因為mock或者一些上下文影響,十分難以調試,這個時候可能會用到skip
和only
API,這兩個API可以只運行某個case或者跳過某個case,可以減少影響面,幫助我們快速調試(而不是注釋代碼)
it.only('should be open', () => {
//...
});
it.skip('should be closed', () => {
//...
});
優雅的retry
vitest中有很多重試的方法,比如:
import { expect, test } from"vitest";
import { sum } from"./sum.js";
it(
"adds 1 + 2 to equal 3",
{
retry: 3,
timeout: 3000,
},
() => {
expect(sum(1, 2)).matchSnapshot();
}
);
it支持傳入第二個配置參數,該參數可以配置retry和timeout等,可以針對一整個case retry的場景。
此外,vitest還支持waifor API,該API可以不斷嘗試和獲取API:
// @vitest-environment jsdom
import { expect, test, vi } from'vitest'
import { getDOMElementAsync, populateDOMAsync } from'./dom.js'
test('Element exists in a DOM', async () => {
// start populating DOM
populateDOMAsync()
const element = await vi.waitFor(async () => {
// try to get the element until it exists
const element = await getDOMElementAsync() as HTMLElement | null
expect(element).toBeTruthy()
expect(element.dataset.initialized).toBeTruthy()
return element
}, {
timeout: 500, // default is 1000
interval: 20, // default is 50
})
expect(element).toBeInstanceOf(HTMLElement)
})
上面這個case會在dom真正渲染出來,才會執行下面的語句,之前會一直輪詢執行語句,判斷是否正確執行。
另外,@testing-library/react里面也有很多類似的API底層其實就是使用waitfor,例如findAllByTextAPI,該API會等待元素渲染完成之后,再繼續進行下面的代碼。
Bad case:
使用盡量詳細的斷言
我遇到一些單測的case,因為沒有運行每個case前mockReset,導致之前spy函數調用記錄仍然存在,于是好幾個case在不同的測試場景中,相同的斷言均是toHaveBeenCalledWith,該斷言因為只會判斷歷史調用中是否存在某參數調用就行了,所以導致這幾個case均成功了,但是實際上是有問題的。
我們應該使用更加詳細的斷言,比如toHaveBeenNthCalledWith或者上文提到的toHaveBeenCalledExactlyOnceWith。
總結
vitest能夠快速火起來,我認為其原因有三點。首先提供了和jest兼容的API設計,這一點對于開源工具來說十分重要,讓用戶可以更少的遷移成本;第二個是擁抱社區,提供了很多很現代的功能,比如瀏覽器模式、源碼內聯測試;第三個基于vite的設計理念,使用現代的API和工具,讓開發者開箱即用ESM和typescript,無需任何其他配置,提供更好的開發者體驗。
我們應該關注vitest最新特性,及時升級vitest,使用最新特性和技術來保證我們代碼的穩定性。