Vite と Jest を組み合わせたテスト環境の最適解

モダンフロントエンド開発において、Vite の高速な開発体験と Jest の豊富なテスト機能を組み合わせることで、効率的で信頼性の高い開発環境を構築できます。しかし、この組み合わせには特有の設定課題があり、多くの開発者が初期導入で躓いてしまいます。本記事では、実際のエラーケースを交えながら、Vite + Jest 環境の最適な構築方法をご紹介します。
背景
Vite の台頭とモダンフロントエンド開発
近年のフロントエンド開発において、Vite は圧倒的な速度でビルド環境のデファクトスタンダードとなりつつあります。ES モジュールのネイティブサポートと esbuild による高速な変換処理により、従来の Webpack ベースの環境と比較して劇的な開発体験の向上を実現しています。
特に以下の点で優れた特徴を持っています:
# | 特徴 | 従来環境との比較 |
---|---|---|
1 | 開発サーバー起動時間 | 数秒 → 数百ミリ秒 |
2 | ホットリロード速度 | 秒単位 → ミリ秒単位 |
3 | バンドルサイズ最適化 | 手動設定 → 自動最適化 |
Jest の位置づけとテスト環境の重要性
Jest は JavaScript エコシステムにおいて最も成熟したテスティングフレームワークの一つです。Facebook(現 Meta)によって開発され、ゼロコンフィグでの導入、豊富なマッチャー、モッキング機能、スナップショットテストなど、包括的なテスト機能を提供しています。
React、Vue、Angular といった主要フレームワークでの標準的な採用により、多くの開発者にとって馴染み深いツールとなっています。
課題
Vite と Jest の互換性問題
Vite と Jest を組み合わせる際に最も頻繁に遭遇するのが、ES モジュールの取り扱いに関する互換性問題です。
代表的なエラーケース 1:ES モジュール導入エラー
swiftSyntaxError: Cannot use import statement outside a module
at wrapSafe (internal/modules/cjs/loader.js:915:16)
at Module._compile (internal/modules/cjs/loader.js:963:27)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1027:10)
このエラーは、Jest がデフォルトで CommonJS モジュールシステムを使用するのに対し、Vite で作成されたコードが ES モジュールを前提としているために発生します。
代表的なエラーケース 2:型定義の競合
rusterror TS2322: Type 'typeof import("vite")' is not assignable to type 'typeof import("jest")'
Property 'defineConfig' is missing in type 'typeof import("jest")' but required in type 'typeof import("vite")'
TypeScript 環境では、Vite と Jest の設定ファイルで異なる型定義が競合することがあります。
設定の複雑さと初期導入の壁
Vite + Jest 環境の構築では、以下のような複数の設定ファイルを適切に調整する必要があります:
# | 設定ファイル | 主な役割 |
---|---|---|
1 | vite.config.ts | Vite ビルド設定 |
2 | jest.config.js | Jest テスト設定 |
3 | tsconfig.json | TypeScript 設定 |
4 | package.json | パッケージ管理とスクリプト |
これらの設定が相互に影響し合うため、一つの設定ミスが全体の動作に影響を与える可能性があります。
解決策
Vite + Jest 環境構築の最適解
この組み合わせを成功させるためには、段階的なアプローチと適切な設定パターンの選択が重要です。
基本方針
- ES モジュール対応の設定統一
- TypeScript 環境の適切な分離
- 依存関係の最小化と最適化
推奨設定パターンの紹介
最も安定した動作を実現する推奨パターンをご紹介します。このパターンでは、Vite と Jest それぞれの強みを活かしながら、互換性問題を最小限に抑えます。
パッケージ依存関係の最適化
まず、必要最小限の依存関係を明確に定義します:
json{
"devDependencies": {
"@types/jest": "^29.5.5",
"@vitejs/plugin-react": "^4.1.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"ts-jest": "^29.1.1",
"typescript": "^5.2.2",
"vite": "^4.4.9"
}
}
この設定では、Jest 環境での React コンポーネントテストに必要な jest-environment-jsdom
と、TypeScript 対応のための ts-jest
を含めています。
具体例
実際の設定ファイル作成手順
ステップ 1:プロジェクト初期化
新規プロジェクトの場合、以下のコマンドで Vite プロジェクトを作成します:
bashyarn create vite my-vite-jest-project --template react-ts
cd my-vite-jest-project
yarn install
ステップ 2:Jest 関連パッケージの追加
次に、テスト環境に必要なパッケージを追加します:
bashyarn add -D jest @types/jest jest-environment-jsdom ts-jest
ステップ 3:Jest 設定ファイルの作成
プロジェクトルートに jest.config.js
を作成し、Vite と Jest の橋渡しを行う設定を記述します:
javascript/** @type {import('jest').Config} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/src/setupTests.ts'],
moduleNameMapping: {
'^@/(.*)$': '<rootDir>/src/$1',
},
transform: {
'^.+\\.tsx?$': 'ts-jest',
},
};
この設定により、TypeScript ファイルの変換とパスエイリアスの解決が適切に行われます。
ステップ 4:TypeScript 設定の調整
Vite 用と Jest 用の TypeScript 設定を分離するため、tsconfig.json
を以下のように設定します:
json{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src"],
"exclude": ["**/*.test.ts", "**/*.test.tsx"]
}
ステップ 5:テスト用 TypeScript 設定
テスト専用の設定として tsconfig.test.json
を作成します:
json{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "CommonJS",
"types": ["jest", "node", "@testing-library/jest-dom"]
},
"include": [
"src/**/*.test.ts",
"src/**/*.test.tsx",
"src/setupTests.ts"
]
}
この分離により、Vite と Jest それぞれに最適化された TypeScript 環境を提供できます。
TypeScript 対応の実装例
セットアップファイルの作成
テスト環境の初期化のため、src/setupTests.ts
を作成します:
typescriptimport '@testing-library/jest-dom';
// グローバルなテスト設定
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn(),
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
サンプルコンポーネントとテスト
実際にテスト可能な React コンポーネントを作成します:
typescript// src/components/Counter.tsx
import { useState } from 'react';
interface CounterProps {
initialValue?: number;
}
export const Counter = ({
initialValue = 0,
}: CounterProps) => {
const [count, setCount] = useState(initialValue);
return (
<div>
<p data-testid='count-display'>Count: {count}</p>
<button
data-testid='increment-button'
onClick={() => setCount(count + 1)}
>
Increment
</button>
<button
data-testid='decrement-button'
onClick={() => setCount(count - 1)}
>
Decrement
</button>
</div>
);
};
対応するテストファイルを作成します:
typescript// src/components/Counter.test.tsx
import {
render,
screen,
fireEvent,
} from '@testing-library/react';
import { Counter } from './Counter';
describe('Counter Component', () => {
test('初期値が正しく表示される', () => {
render(<Counter initialValue={5} />);
expect(
screen.getByTestId('count-display')
).toHaveTextContent('Count: 5');
});
test('インクリメントボタンが正しく動作する', () => {
render(<Counter />);
const incrementButton = screen.getByTestId(
'increment-button'
);
fireEvent.click(incrementButton);
expect(
screen.getByTestId('count-display')
).toHaveTextContent('Count: 1');
});
});
package.json スクリプトの設定
テスト実行のための npm スクリプトを package.json
に追加します:
json{
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
}
}
よくある設定エラーの解決
実際の導入過程で遭遇する可能性のあるエラーと解決方法をご紹介します:
エラー例 1:モジュール解決の失敗
arduinoCannot find module '@/components/Counter' from 'src/components/Counter.test.tsx'
このエラーは、Jest がパスエイリアスを正しく解決できない場合に発生します。解決には、jest.config.js
の moduleNameMapping
設定を確認してください。
エラー例 2:JSX の変換エラー
javascriptSyntaxError: Unexpected token '<'
JSX の変換設定が不適切な場合に発生します。ts-jest
の設定で以下を追加します:
javascriptmodule.exports = {
// 既存設定...
globals: {
'ts-jest': {
tsconfig: {
jsx: 'react-jsx',
},
},
},
};
まとめ
Vite と Jest を組み合わせたテスト環境は、適切な設定により非常に強力な開発体験を提供します。重要なポイントは以下の通りです:
- ES モジュールと CommonJS の適切な使い分け:Vite と Jest それぞれに最適化された設定の分離
- TypeScript 設定の最適化:開発用とテスト用の設定ファイルの分離による競合回避
- 段階的な導入アプローチ:基本設定から始めて、必要に応じて高度な機能を追加
これらの設定により、高速な開発サーバーと信頼性の高いテスト環境を両立できます。特に大規模なプロジェクトにおいて、この組み合わせの恩恵を強く感じられるでしょう。
モダンフロントエンド開発において、テスト環境の充実は品質向上に直結します。今回ご紹介した設定パターンを参考に、ぜひプロジェクトに最適な Vite + Jest 環境を構築してみてください。
関連リンク
- blog
Culture, Automation, Measurement, Sharing. アジャイル文化を拡張する DevOps の考え方
- blog
開発と運用、まだ壁があるの?アジャイルと DevOps をかけ合わせて開発を爆速にする方法
- blog
スクラムマスターは雑用係じゃない!チームのポテンシャルを 120%引き出すための仕事術
- blog
「アジャイルやってるつもり」になってない?現場でよく見る悲劇と、僕らがどう乗り越えてきたかの話
- blog
強いチームは 1 日にしてならず。心理的安全性を育むチームビルディングの鉄則
- blog
トヨタ生産方式から生まれた「リーン」。アジャイル開発者が知っておくべきその本質
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来
- review
人類はなぜ地球を支配できた?『サピエンス全史 上巻』ユヴァル・ノア・ハラリが解き明かす驚愕の真実