Vitest への移行ガイド:Jest からの段階的移行と実装パターン
Jest から Vitest へ移行する手順を実務視点で解説。設定ファイル・テストコード・カバレッジ・CI 統合まで、既存プロジェクトで即使える段階的移行ガイド。

Vitest への移行ガイド:Jest からの段階的移行と実装パターン
Jest は長らく JavaScript / TypeScript のテストツールとして標準的な地位を占めてきましたが、ビルドツールの進化に伴い、Vite エコシステムと統合された Vitest が急速に普及しています。
本記事では、既存の Jest プロジェクトを Vitest へ段階的に移行する手順と、実務で遭遇する典型的な課題への対処方法を解説します。
1. Vitest を選ぶ理由と移行判断基準
Vitest の主要な利点
| 項目 | Jest | Vitest | |------|------|--------| | 起動速度 | 遅い(数秒〜数十秒) | 高速(1秒以下) | | ビルドツール統合 | 別途設定が必要 | Vite 設定を再利用 | | ESM サポート | 実験的・制約あり | ネイティブ対応 | | HMR(ホットリロード) | なし | あり(watch モード) | | 並列実行 | ワーカープロセス | スレッド(より軽量) | | 設定ファイル | jest.config.js | vite.config.ts で統合可能 |
移行を検討すべきプロジェクト
- ✅ Vite / Vite ベースのフレームワーク(Next.js App Router, SvelteKit など)を使用
- ✅ テスト実行速度に課題を感じている(CI で 5 分以上かかる)
- ✅ ESM パッケージを多用(Jest の設定が煩雑)
- ✅ 開発体験を重視(watch モードでの高速フィードバック)
移行を急がなくてよいケース
- ❌ Jest の独自機能に強く依存(jest.mock のホイスティング挙動など)
- ❌ 既存テストが数千件あり、移行コストが見合わない
- ❌ React Native など Vite 非対応環境
2. 移行前の準備:互換性チェック
2-1. 依存パッケージの確認
# Jest 関連パッケージを列挙
npm list --depth=0 | grep jest
# 以下が一般的な構成
# jest
# @types/jest
# ts-jest (TypeScript の場合)
# @testing-library/jest-dom
# jest-environment-jsdom
2-2. テストファイルのパターン調査
# テストファイルの命名規則を確認
find . -name '*.test.*' -o -name '*.spec.*' | head -n 10
# Jest の設定ファイルを確認
cat jest.config.js
2-3. モック利用状況の確認
// Jest 固有のモック API を使っている箇所を検索
// jest.mock() のホイスティング挙動に依存していないか確認
grep -r "jest.mock" src/
grep -r "jest.spyOn" src/
重要な違い:Vitest の vi.mock() は Jest ほど自動ホイスティングされない ため、import 文の順序に注意が必要です。
3. Vitest のインストールと基本設定
3-1. パッケージのインストール
# Vitest 本体と型定義
npm install -D vitest @vitest/ui
# DOM テスト用(React / Vue などの場合)
npm install -D jsdom @testing-library/react @testing-library/user-event
# カバレッジ計測用
npm install -D @vitest/coverage-v8
3-2. vite.config.ts に統合
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
test: {
// グローバル API を有効化(describe, it, expect など)
globals: true,
// DOM 環境が必要な場合
environment: 'jsdom',
// セットアップファイル
setupFiles: ['./src/test/setup.ts'],
// カバレッジ設定
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
exclude: [
'node_modules/',
'src/test/',
'**/*.test.{ts,tsx}',
'**/*.spec.{ts,tsx}',
],
},
// タイムアウト設定
testTimeout: 10000,
},
});
3-3. セットアップファイルの作成
// src/test/setup.ts
import { expect, afterEach } from 'vitest';
import { cleanup } from '@testing-library/react';
import * as matchers from '@testing-library/jest-dom/matchers';
// jest-dom のマッチャーを追加
expect.extend(matchers);
// 各テスト後に DOM をクリーンアップ
afterEach(() => {
cleanup();
});
3-4. TypeScript 型定義の追加
// tsconfig.json
{
"compilerOptions": {
"types": ["vitest/globals", "@testing-library/jest-dom"]
}
}
4. テストコードの段階的移行
4-1. 基本的な移行パターン
| Jest | Vitest | 備考 |
|------|--------|------|
| jest.fn() | vi.fn() | モック関数 |
| jest.mock() | vi.mock() | モジュールモック |
| jest.spyOn() | vi.spyOn() | スパイ |
| jest.useFakeTimers() | vi.useFakeTimers() | タイマーモック |
| jest.requireActual() | vi.importActual() | 実モジュールの取得 |
4-2. 自動置換スクリプト
# 一括置換(macOS / Linux)
find src -name '*.test.*' -o -name '*.spec.*' | xargs sed -i '' 's/jest\.fn/vi.fn/g'
find src -name '*.test.*' -o -name '*.spec.*' | xargs sed -i '' 's/jest\.mock/vi.mock/g'
find src -name '*.test.*' -o -name '*.spec.*' | xargs sed -i '' 's/jest\.spyOn/vi.spyOn/g'
注意:jest.mock() のホイスティング挙動に依存している場合は手動調整が必要です。
4-3. モックのホイスティング問題への対処
Jest の挙動:
import { getData } from './api';
jest.mock('./api'); // ← これが自動的にファイル先頭に巻き上げられる
test('should fetch data', () => {
getData.mockResolvedValue({ id: 1 });
});
Vitest での修正:
import { vi } from 'vitest';
// モックを先に宣言
vi.mock('./api', () => ({
getData: vi.fn(),
}));
import { getData } from './api';
test('should fetch data', () => {
vi.mocked(getData).mockResolvedValue({ id: 1 });
});
4-4. タイマーモックの移行
// Jest
jest.useFakeTimers();
setTimeout(() => console.log('done'), 1000);
jest.advanceTimersByTime(1000);
jest.useRealTimers();
// Vitest
import { vi } from 'vitest';
vi.useFakeTimers();
setTimeout(() => console.log('done'), 1000);
vi.advanceTimersByTime(1000);
vi.useRealTimers();
5. よくある移行課題と対処法
5-1. @testing-library/jest-dom のマッチャーエラー
エラー例:
Property 'toBeInTheDocument' does not exist on type 'Assertion'
対処法:
セットアップファイルで明示的に extend する(前述の setup.ts を参照)。
5-2. ESM パッケージのモック
// named export のモック
vi.mock('axios', () => ({
default: {
get: vi.fn(),
post: vi.fn(),
},
}));
// または動的インポートを使用
const axios = await vi.importActual<typeof import('axios')>('axios');
vi.mock('axios', () => ({
default: {
...axios.default,
get: vi.fn(),
},
}));
5-3. グローバル変数へのアクセス
// Jest
global.fetch = jest.fn();
// Vitest
import { vi } from 'vitest';
globalThis.fetch = vi.fn();
// または
vi.stubGlobal('fetch', vi.fn());
5-4. スナップショットテストの移行
// Jest のスナップショットはそのまま使える
import { expect, test } from 'vitest';
test('matches snapshot', () => {
const result = { id: 1, name: 'test' };
expect(result).toMatchSnapshot();
});
注意:スナップショットファイルの形式は互換性がありますが、ファイル名が変わる場合があります(__snapshots__ ディレクトリは共通)。
6. パフォーマンス最適化と並列実行
6-1. 並列実行の設定
// vite.config.ts
export default defineConfig({
test: {
// ファイル並列実行(デフォルト: true)
fileParallelism: true,
// 単一ファイル内の並列実行(デフォルト: false)
sequence: {
concurrent: true,
},
// 最大並列ワーカー数
maxConcurrency: 5,
},
});
6-2. 遅いテストの特定
# レポーター設定でテスト時間を表示
npx vitest --reporter=verbose
// vite.config.ts
export default defineConfig({
test: {
reporters: ['verbose'],
outputFile: './test-results.json',
},
});
6-3. UI モードでのデバッグ
# ブラウザベースの UI でテストを実行
npx vitest --ui
利点:
- テストの依存関係を可視化
- 個別テストの再実行が簡単
- カバレッジをリアルタイム確認
7. CI/CD への統合
7-1. GitHub Actions の設定例
name: Test
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests with coverage
run: npm run test:coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
files: ./coverage/coverage-final.json
fail_ci_if_error: true
7-2. package.json のスクリプト
{
"scripts": {
"test": "vitest",
"test:ui": "vitest --ui",
"test:coverage": "vitest run --coverage",
"test:watch": "vitest --watch"
}
}
7-3. カバレッジしきい値の設定
// vite.config.ts
export default defineConfig({
test: {
coverage: {
provider: 'v8',
thresholds: {
lines: 80,
functions: 80,
branches: 75,
statements: 80,
},
},
},
});
8. 段階的移行戦略
8-1. 推奨移行手順
| フェーズ | 作業内容 | 期間目安 | |---------|---------|----------| | 1. 検証 | 小規模なテストファイル 5〜10 件で動作確認 | 1 日 | | 2. 基盤整備 | vite.config.ts・setup.ts・CI 設定 | 2〜3 日 | | 3. 自動移行 | モック API の一括置換スクリプト実行 | 1 日 | | 4. 手動調整 | ホイスティング問題・ESM モックの修正 | 3〜5 日 | | 5. 並行運用 | Jest と Vitest を 2 週間併用 | 2 週間 | | 6. Jest 削除 | jest 関連パッケージのアンインストール | 1 日 |
8-2. 並行運用のための設定
// package.json
{
"scripts": {
"test:jest": "jest",
"test:vitest": "vitest run",
"test": "npm run test:vitest"
}
}
メリット:
- リグレッションを早期発見
- チーム全体の学習期間を確保
- 問題発生時の切り戻しが容易
9. まとめ:移行判断のチェックリスト
✅ 移行前に確認すべき項目
- [ ] Vite を使用している、または導入予定
- [ ] テスト実行速度に課題がある(CI で 3 分以上)
- [ ] Jest の独自機能への依存が少ない
- [ ] チームの学習コストを吸収できる
- [ ] 1〜2 週間の移行期間を確保できる
✅ 移行後に得られる効果
- [ ] テスト実行時間が 50〜80% 削減
- [ ] watch モードでの開発体験向上
- [ ] Vite 設定との統合で管理コスト削減
- [ ] ESM パッケージの扱いがシンプルに
✅ 移行時の注意点
- [ ] モックのホイスティング挙動の違いを理解
- [ ] 段階的移行で並行運用期間を設ける
- [ ] CI/CD パイプラインの調整を忘れずに
- [ ] カバレッジしきい値を再設定
Vitest は Jest の優れた後継ツールですが、プロジェクトの状況によっては移行コストが見合わないケースもあります。本記事の判断基準とチェックリストを参考に、適切なタイミングでの移行を検討してください。
Yureate では、テスト戦略の見直しや CI/CD パイプラインの最適化もお手伝いしています。技術選定や移行支援のご相談は、お気軽にお問い合わせください。
