JestからVitestへの移行でテスト実行速度が劇的に改善した話
こんにちは、グロースエクスパートナーズの@gxp-nyakyamuraです。
自分の参加している案件のフロントエンドのテスト環境をJestからVitestに移行したところ、テスト実行速度が非常に改善されました。(10.715秒 => 2.45秒)
本記事では、今回どのように移行を行ったか、その手順などをざっくりとまとめました。
今回の移行はあまり工数をかけていないのでそこまで詳しい調査はできておらず、本記事のやり方がベストプラクティスではない可能性は十分にありますのでご了承ください。
導入・背景
今回対象となる案件ではユニットテストをJestで行っていましたが、コミットごとにテストが実行される設定(husky)のため、毎回10秒以上待たされるストレスがありました。今後、テスト数の増加が予想されることも踏まえ、早期の改善を目指し、Vitestへの移行を検討しました。
プロジェクトについて
技術スタック・テスト対象
今回の案件では、
Vue3・TypeScript・Vite を使ったSPAを開発しています。
なお、現状はVueコンポーネントのテストは未導入であり、ロジックやユーティリティ層のTypeScriptファイルをテスト対象としています。
テスト対象となる TypeScript ファイルが 100 ファイル弱(型定義, router, i18nなどを除く)、約10,000行以上(blank/コメント含まず)と中規模の構成です。
テストコード(.spec.ts)は11ファイル、it/testで定義されたテストは191個 実装しています
カバレッジ率の現状
本記事執筆時点でカバレッジを初めて算出したところ、約15.5% という結果でした。(vueコンポーネントのテストは未導入のため、算出に含めていない)
ユニットテストの導入が途中からだったこと、またカバー範囲がまだ狭いこともあり、今後さらに充実させていく方針です。
そのためにも、早い段階でVitestへ移行し、テスト実行速度を向上させておくことが重要だと判断しました。
カバレッジの計測には、Vitest 導入後に @vitest/coverage-v8
を利用し、
以下のように vitest.config.ts
でカバレッジ対象ファイルや除外ファイルを明示的に指定しています。
coverage: {
provider: 'v8',
include: ['src/**/*.{ts,js}'],
exclude: [
'**/*.vue',
'**/*.d.ts',
'**/*.spec.ts',
'**/*.test.ts',
'**/__mocks__/**',
'dist/**',
'build/**',
'**/*.config.*',
'**/vite-env.d.ts',
],
},
設定を適切に行わないと、テスト用や型定義ファイルまでカバレッジ計算に含まれてしまい、実態と異なる値になってしまうため注意が必要です。
JestとVitestの特徴比較
実際に両方のテストランナーを比較してみると、以下のような違いがあります:
指標 | Jest | Vitest |
---|---|---|
初回リリース | 2014年 | 2021年 |
GitHub Star数 | 44.8k | 14.5k |
npmパッケージ(キーワード) | 218(npm検索) | 12(npm検索) |
npm DL数 | どの期間をとってもJestの方が多い | |
Bing検索結果数 | 約1,920,000件 | 約69,400件 |
Jestの特徴
メリット:
- 長期間利用されており、安定性と信頼性が高い。
- エコシステムが豊富で、さまざまなユースケースやフレームワークに対応している。
- ドキュメントが充実しており、学習リソースが多い。
デメリット:
- 実行速度が比較的遅い。
Vitestの特徴
メリット:
- Viteの高速な開発サーバーやesbuildを利用し、テストの実行速度が非常に速い。
- キャッシュを効率的に利用しているため、繰り返しのテスト実行が高速。
- esbuild によってESM, TS が標準サポートされている。
- Jestとの互換性が高く、Jestからの移行コストが非常に低い。
- 設定が簡単でシンプルに済む。
デメリット:
- 比較的新しいフレームワークであり、エコシステムやドキュメントがJestほど充実していない。
- Viteベースであるため、Viteが対応していない一部のレガシーな依存関係や特殊な設定に問題が生じる場合がある。
移行の手間は少なかった
VitestはJestとAPIが非常に似ており、移行コストが非常に低かったです。
主な変更点は以下の通りです。
- JestのコマンドをVitestのコマンドに変更。(package.json)
- "test": "jest"
+ "test": "vitest run"
vitest run --coverage
にしたら、@vitest/coverage-v8
によってカバレッジ率のレポートが出力されます。
- モックの書き方はほぼ同じで、簡単な置き換えで済みました。
- jest.mock('@/module')
+ vi.mock('@/module')
- import文の一部修正
- const { sampleFunc } = require('../src/utils/sample')
+ import { sampleFunc } from '@/utils/sample'
requireのままだとテストが動かず、importに修正した箇所があります。
設定ファイルは基本的にはそのまま流用可能でした。
- tsconfig.jsonの修正
"types": [
- "jest",
+ "vitest/globals"
]
- vitestの設定(vitest.config.ts)
+import { resolve } from 'path'
+import AutoImport from 'unplugin-auto-import/vite'
+import { configDefaults, defineConfig, mergeConfig } from 'vitest/config'
+
+import viteConfig from './vite.config'
+
+export default defineConfig((configEnv) =>
+ mergeConfig(
+ viteConfig(configEnv),
+ defineConfig({
+ test: {
+ exclude: [...configDefaults.exclude, 'packages/template/*'],
+ globals: true,
+ environment: 'jsdom',
+ setupFiles: ['@vitest/web-worker', 'vitest-localstorage-mock'],
+ mockReset: false,
+ coverage: {
+ provider: 'v8',
+ include: ['src/**/*.{ts,js}'],
+ exclude: [
+ '**/*.vue',
+ '**/*.d.ts',
+ '**/*.spec.ts',
+ '**/*.test.ts',
+ '**/__mocks__/**',
+ 'dist/**',
+ 'build/**',
+ '**/*.config.*',
+ '**/vite-env.d.ts',
+ ],
+ },
+ },
+ plugins: [
+ AutoImport({
+ imports: ['vitest'],
+ dts: true, // generate TypeScript declaration
+ }),
+ ],
+ resolve: {
+ alias: [
+ {
+ find: '@/scripts/sample_worker?worker&inline',
+ replacement: resolve(__dirname, '__mocks__/sampleWorkerMock.ts'),
+ },
+ ],
+ },
+ }),
+ ),
+)
vitestのconfigの書き方は他にもあるようですが、公式ドキュメント(+Chat GPT)を参考にして上記のように設定しました。
ちなみに、environment
を記述しなければ、デフォルトでnode
となります。その場合、node環境では動かないテスト(DOM APIなど)については、ファイルの冒頭に以下の記述を加えて個別にjsdom環境を適用する必要があります。それはちょっと面倒なので、グローバル設定でjsdomを適用させました。
/**
* @vitest-environment jsdom
*/
- パッケージの差分
- "@types/jest": "^29.5.14",
- "jest": "^29.7.0",
- "ts-jest": "^29.2.5",
- "jest-environment-jsdom": "30.0.0-beta.3",
+ "@vitest/coverage-v8": "^3.2.3",
+ "@vitest/web-worker": "^3.2.3",
+ "vitest": "^3.2.3",
+ "vitest-localstorage-mock": "^0.1.2",
vitest単体だと動かず、WorkerやLocalstorageに対応するためのライブラリを追加で導入しました。
今まではカバレッジ率の計算はしていなかったのですが、本記事を書くにあたって@vitest/coverage-v8をインストールして確認してみました。せっかくカバレッジ率が見られるようになったので付けました。
移行結果のパフォーマンス比較
ローカル環境では以下のように劇的な速度改善が見られました。
テスト環境 | 実行時間 |
---|---|
Jest | 10.715秒 |
Vitest | 2.45秒 |
GitHub Actionsでは約50秒のままでしたが(原因などは未調査)、ローカルでの改善効果は明確でした。
移行後に判明したこと
案件のリポジトリ全体の依存関係の数は少し増加しました。依存関係数の推移は以下の通りです。
- Jestのとき: 28044
- Vitest移行後: 28490
まとめ
JestからVitestへの移行は非常に手軽で効果的でした。小さな変更で大幅なパフォーマンス改善が得られ、移行してとてもよかったと感じています。今後さらにテストを充実させるための良い基盤が整いました。