TurborepoモノレポアーキテクチャにおけるJestからVitestへの移行

Table of Contents
エンタープライズのコードベースが大規模なTypeScriptモノレポにスケールするにつれて、テスト実行速度は開発者の生産性を決定する最大の要因となります。プルリクエストで、相互接続された数十のパッケージにわたる500以上の単体テストおよび統合テストスイートの実行が必要な場合、遅いテストパイプラインは継続的インテグレーションを麻痺させ、ローカル開発を停滞させます。
約10年間、JestはJavaScriptのデフォルトのテストフレームワークでした。しかし、Turborepoで管理されるTypeScriptモノレポと組み合わせると、Jestは深刻なアーキテクチャ上のボトルネックを露呈します。冗長なBabel/ts-jestコンパイルステップ、アプリケーションバンドラーとは別のトランスフォーマーパイプライン、そして並列テストワーカー実行中の大規模なメモリ消費です。
Viteの超高速開発サーバーとESモジュール変換パイプラインの上に直接構築されたVitestは、重複するコンパイルを排除し、桁違いに高速な実行を可能にするドロップイン代替品を提供します。
このガイドでは、プロダクションのTurborepoワークスペース内でJestからVitestへのエンドツーエンドの移行について、ワークスペース構成、モック変換、CIキャッシュ戦略を含めて説明します。
JestがTurborepoワークスペースのボトルネックになる理由
20個のパッケージを持つTypeScriptモノレポでは、Jestは通常、インポートされたすべてのファイルに対してts-jestまたは@babel/preset-typescriptを呼び出すことでテストを実行します。ビルドツールがVite、esbuild、またはSWCを使用してプロダクションコードをコンパイルしている場合でも、Jestは独自の分離されたトランスパイルパイプラインを維持します。
[Legacy Monorepo Pipeline]
Build Tool (Vite / ESBuild) ──► Fast ESM Compilation ──► Production Bundle
▲
(Duplicated effort)
▼
Testing Tool (Jest + ts-jest) ──► Slow CJS Compilation ──► Test Runner (High Memory)
この分割されたアーキテクチャは、3つの主要な問題点を作り出します。
- 冗長なコンパイル: すべてのテストワーカーが、同じ共有TypeScriptライブラリをメモリ内で独立して再コンパイルします。
- ESM/CommonJSの非互換性: 純粋なESM(
node-fetch、chalk、またはnanoidの最新バージョンなど)を出荷するパッケージをテストするには、jest.config.jsで複雑なtransformIgnorePatterns正規表現構成が必要です。 - メモリリークとワーカーのオーバーヘッド: Jestの分離されたVMランナーは、大規模なテストスイートでメモリリークを引き起こし、
--runInBandが強制されない限り(これにより並列処理が破壊されます)、GitHub Actionsランナーで頻繁にJavaScript heap out of memoryエラーにつながります。
モノレポにおけるVitestの利点
Vitestは、Viteとまったく同じ変換パイプライン、プラグインエコシステム、および構成を共有することで、これらのアーキテクチャ上の問題を解決します。
- 単一パイプライン: VitestはViteの事前設定された変換キャッシュを使用します。Viteがパスエイリアス(
@/components/*)の解決方法やTSXのコンパイル方法を知っていれば、Vitestは設定のずれなしに同じように処理します。 tinypoolを介したワーカープール: Vitestは、重いNodeJS子プロセスではなく軽量なワーカー スレッドを利用し、プロセス生成のオーバーヘッドを削減します。- ネイティブESMとViteワークスペース: VitestはECMAScriptモジュールをネイティブに理解し、
vitest.workspace.tsを介したマルチプロジェクトワークスペースをネイティブにサポートします。
ステップバイステップ移行ガイド
ステップ1: Jestの依存関係をクリーンアップする
モノレポのルートからJest、ts-jest、Jest型定義、およびBabelトランスフォーマーを削除します。
# In your monorepo root
npm uninstall jest @types/jest ts-jest babel-jest
npm install -D vitest @vitest/ui @vitest/coverage-v8
ステップ2: ルートのvitest.workspace.tsを構成する
すべてのサブパッケージで分離されたテストランナーバイナリを維持するのではなく、すべてのアプリとパッケージを検出する統一されたルートワークスペースファイルを定義します。
// vitest.workspace.ts
import { defineWorkspace } from 'vitest/config';
export default defineWorkspace([
'apps/*/vite.config.ts',
'apps/*/vitest.config.ts',
'packages/*/vitest.config.ts',
]);
ステップ3: 標準化されたパッケージ構成を作成する
各パッケージ(例: packages/utils/vitest.config.ts)内に、共有設定を拡張する集中的な構成を作成します。
// packages/utils/vitest.config.ts
import { defineConfig } from 'vitest/config';
import path from 'node:path';
export default defineConfig({
test: {
globals: true,
environment: 'node', // Use 'happy-dom' or 'jsdom' for UI packages
include: ['src/**/*.{test,spec}.{ts,tsx}'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
exclude: ['node_modules/**', 'dist/**'],
},
alias: {
'@': path.resolve(__dirname, './src'),
},
},
});
ReactまたはNext.jsコンポーネントをテストするフロントエンドパッケージの場合、happy-dom(jsdomよりも大幅に高速です)をインストールし、次のように指定します。
// packages/ui/vitest.config.ts
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'happy-dom',
setupFiles: ['./src/test/setup.ts'],
},
});
API変換のモック: jestからviへ
Vitestは、viオブジェクトを介して1対1互換のモックユーティリティを提供します。
| Jest API | Vitest API | Notes |
|---|---|---|
jest.fn() | vi.fn() | 同じシグネチャと呼び出し追跡 |
jest.spyOn() | vi.spyOn() | 完全な型保持 |
jest.mock() | vi.mock() | Vitestはvi.mockを自動的に巻き上げます |
jest.useFakeTimers() | vi.useFakeTimers() | 内部的に@sinonjs/fake-timersを使用します |
jest.clearAllMocks() | vi.clearAllMocks() | モック履歴をクリアします |
例: 非同期サービスのモック
// user-service.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { fetchUserProfile } from './user-service';
// Mock external HTTP client module
vi.mock('./api-client', () => ({
apiClient: {
get: vi.fn(),
},
}));
import { apiClient } from './api-client';
describe('fetchUserProfile', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('fetches and transforms user details correctly', async () => {
const mockUser = { id: 'usr_123', name: 'Alex Doe', email: 'alex@example.com' };
vi.mocked(apiClient.get).mockResolvedValueOnce({ data: mockUser });
const result = await fetchUserProfile('usr_123');
expect(apiClient.get).toHaveBeenCalledWith('/users/usr_123');
expect(result).toEqual({
id: 'usr_123',
displayName: 'Alex Doe',
});
});
});
Turborepoパイプラインキャッシュの構成
コードが変更されていない場合にテスト実行がキャッシュされるように、turbo.jsonを更新します。
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"test": {
"dependsOn": ["^build"],
"inputs": ["src/**/*.ts", "src/**/*.tsx", "test/**/*.ts", "vitest.config.ts"],
"outputs": ["coverage/**"]
},
"test:watch": {
"cache": false,
"persistent": true
}
}
}
これで、モノレポのルートからnpx turbo run testを実行すると、すべてのパッケージテストが並行して実行され、変更されていないパッケージでは即座にキャッシュヒットします。
# Run all tests across the monorepo
npx turbo run test
# Run tests only for a specific package and its dependents
npx turbo run test --filter=@repo/ui...
実世界のCIベンチマーク: Jest vs Vitest
以下のベンチマークは、GitHub Actions(ubuntu-latest、2 vCPU)で実行されている18個のパッケージと1,240個のテストケースを含むプロダクションモノレポから収集されました。
| 測定指標 | Jest (ts-jest) | Vitest (v8プロバイダー) | 高速化係数 |
|---|---|---|---|
| コールドCI実行 (キャッシュなし) | 4分18秒 | 34秒 | 7.6倍高速 |
| ウォームCI実行 (Turborepoキャッシュ) | 2分05秒 | 4.2秒 | 30倍高速 |
| ピークメモリ消費量 | 2.1 GB RAM | 480 MB RAM | 77%メモリ削減 |
| ウォッチモードのフィードバック遅延 | 2,800 ms | 180 ms | ほぼ瞬時 |
よくある質問
describeとitのインポートを避けるために、Vitestをglobals: trueで実行できますか?
はい。vitest.config.tsでtest: { globals: true }を有効にすると、describe、it、expect、およびviがグローバルに利用可能になり、Jestのデフォルトの動作と一致します。ただし、TypeScriptがグローバル識別子を認識するように、tsconfig.json compilerOptionsに"types": ["vitest/globals"]を追加する必要があります。
VitestはCommonJSのみのサードパーティライブラリをどのように処理しますか?
Viteは、esbuildを使用した自動プリバンドルによってCommonJSの依存関係を処理します。もし、テスト実行中に難解なCommonJSライブラリが解決エラーを引き起こす場合、vitest.config.tsのtest: { server: { deps: { inline: ['legacy-package-name'] } } }に明示的に追加できます。
統合されたモノレポのカバレッジレポートを生成するにはどうすればよいですか?
ルートワークスペースからvitest run --coverageを実行します。Vitestは、すべてのパッケージのカバレッジデータを統合されたV8またはIstanbulカバレッジディレクトリに収集し、CodecovまたはSonarQubeに直接アップロードできます。
こちらもおすすめ
Free In-Browser Developer Tools
Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.
Related Articles

Vitestモノレポ単体テストとパフォーマンス最適化 (2026)
大規模TypeScriptモノレポにおけるVitestのパフォーマンスを、スレッドプール、barrel file imports、isolation flags、スマートキャッシュで最適化する実用ガイド。
Read more
fireEventを使うのをやめよう: React Testing user-event v14ガイド
React Testing LibraryでfireEventを使うのをやめるべき理由を、@testing-library/user-event v14、userEvent.setup()、非同期タイピング、アクセシブルなクエリ、MSWモックの完全ガイドで解説します。
Read more
PlaywrightE2Eテスト:フレークのないテストを実現する4つのルール
sleep(5000)の使用をやめ、Playwrightの自動待機、分離された並列ブラウザコンテキスト、トレースビューアを習得して、CI/CDテスト自動化を完璧にしましょう。
Read more