Vitest vs Jest — 2025年版 JavaScript テストフレームワーク徹底比較
1. 結論
Vite ベースのプロジェクトなら Vitest を選ぶのが最適解です。 既存の大規模プロジェクトで Jest 資産が豊富にある場合は、無理に移行せず Jest を継続するのが現実的です。新規プロジェクトであれば、速度・DX(開発者体験)の面から Vitest を第一候補にすることをおすすめします。
2. 比較表
| 観点 | Vitest | Jest |
|---|---|---|
| 初回リリース | 2022年 | 2014年 |
| 最新バージョン(2025年6月時点) | v3.x | v30.x |
| npm 週間DL数 | 約 1,200万 | 約 3,500万 |
| バンドラー / トランスフォーマー | Vite (esbuild / Rollup) | babel-jest(デフォルト) |
| ESM ネイティブ対応 | ✅ 完全対応 | ⚠️ 実験的(--experimental-vm-modules) |
| TypeScript 対応 | ✅ 設定不要で動作 | ⚠️ ts-jest or @swc/jest が必要 |
| 設定ファイル | vitest.config.ts(Vite 設定と統合可) | jest.config.ts |
| Watch モード速度 | ⚡ 非常に高速(HMR ベース) | 普通 |
| ブラウザテスト | ✅ @vitest/browser で対応 | ❌ jsdom / happy-dom のみ |
| スナップショットテスト | ✅ Jest 互換 | ✅ 本家 |
| モック | ✅ vi オブジェクト(Jest 互換 API) | ✅ jest オブジェクト |
| カバレッジ | @vitest/coverage-v8 / istanbul | --coverage(istanbul / v8) |
| UI | ✅ @vitest/ui(ブラウザ UI) | ❌ なし(サードパーティ) |
| 並列実行 | ✅ ワーカースレッド / マルチプロセス | ✅ ワーカープロセス |
| インラインテスト | ✅ if (import.meta.vitest) で可能 | ❌ 非対応 |
| エコシステム・情報量 | 急成長中 | 非常に豊富 |
| 学習コスト | 低い(Jest 経験者ならほぼゼロ) | 低い(デファクト標準) |
| Jest からの移行コスト | 低い(API 互換レイヤーあり) | — |
3. それぞれの強み
Vitest の強み
- 圧倒的な速度: Vite の HMR パイプラインを活用し、変更ファイルのみを高速に再実行します。大規模プロジェクトほど差が顕著です。
- ESM ファースト:
import/exportをネイティブに扱えるため、ESM 移行済みのコードベースでハック的な設定が不要です。 - TypeScript がゼロコンフィグ: esbuild による型ストリップが内蔵されており、
ts-jestのような追加パッケージが要りません。 - Vite 設定との統合:
vite.config.tsにtestブロックを追加するだけで済み、エイリアスやプラグインを二重管理する必要がありません。 - インラインテスト: ソースファイル内にテストを書ける独自機能があり、ユーティリティ関数のコロケーションに便利です。
- ブラウザモード: Playwright や WebDriverIO と連携し、実際のブラウザ上でテストを実行できます。
Jest の強み
- 圧倒的な実績と情報量: 10年以上の歴史があり、Stack Overflow・ブログ記事・書籍の情報が桁違いに豊富です。
- 巨大なエコシステム:
jest-extended、jest-dom、jest-image-snapshotなど、サードパーティマッチャーやプラグインが充実しています。 - フレームワーク公式サポート: Create React App、Angular CLI、NestJS など多くのフレームワークがデフォルトで Jest を採用しています。
- 安定性: メジャーバージョン間の破壊的変更が少なく、長期運用プロジェクトで安心感があります。
--shardによる CI 分散: 大規模 CI で複数マシンにテストを分散する仕組みが成熟しています(※ Vitest にも同機能あり)。
4. コード例で比較
題材: 非同期関数 fetchUser のテスト
まず、テスト対象のコードです。
// src/fetchUser.ts
export interface User {
id: number;
name: string;
}
export async function fetchUser(id: number): Promise<User> {
const res = await fetch(`https://api.example.com/users/${id}`);
if (!res.ok) throw new Error(`User ${id} not found`);
return res.json();
}
Vitest で書いた場合
// src/__tests__/fetchUser.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { fetchUser } from '../fetchUser';
describe('fetchUser', () => {
beforeEach(() => {
vi.restoreAllMocks();
});
it('ユーザーを正常に取得できる', async () => {
const mockUser = { id: 1, name: 'Taro' };
// グローバル fetch をモック
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve(mockUser),
}));
const user = await fetchUser(1);
expect(user).toEqual(mockUser);
expect(fetch).toHaveBeenCalledWith('https://api.example.com/users/1');
});
it('404 の場合はエラーを投げる', async () => {
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
ok: false,
status: 404,
}));
await expect(fetchUser(999)).rejects.toThrow('User 999 not found');
});
});
実行コマンド:
npx vitest run
# Watch モード
npx vitest
Jest で書いた場合
// src/__tests__/fetchUser.test.ts
import { fetchUser } from '../fetchUser';
describe('fetchUser', () => {
beforeEach(() => {
jest.restoreAllMocks();
});
it('ユーザーを正常に取得できる', async () => {
const mockUser = { id: 1, name: 'Taro' };
// グローバル fetch をモック
jest.spyOn(global, 'fetch').mockResolvedValue({
ok: true,
json: () => Promise.resolve(mockUser),
} as Response);
const user = await fetchUser(1);
expect(user).toEqual(mockUser);
expect(global.fetch).toHaveBeenCalledWith('https://api.example.com/users/1');
});
it('404 の場合はエラーを投げる', async () => {
jest.spyOn(global, 'fetch').mockResolvedValue({
ok: false,
status: 404,
} as Response);
await expect(fetchUser(999)).rejects.toThrow('User 999 not found');
});
});
実行コマンド:
npx jest
# Watch モード
npx jest --watch
コード比較のポイント
| 観点 | Vitest | Jest |
|---|---|---|
| import 文 | vi を明示的に import | グローバル jest が自動注入 |
| モック関数 | vi.fn() / vi.stubGlobal() | jest.fn() / jest.spyOn() |
| API の互換性 | Jest とほぼ同一 | — |
| TypeScript 実行 | 追加設定なし | ts-jest or @swc/jest が必要 |
ご覧のとおり、テストコード自体はほぼ同じです。jest → vi に置き換えるだけで動くケースが大半であり、移行コストの低さが Vitest の大きな魅力です。
5. どちらを選ぶべきか — ユースケース別ガイド
✅ Vitest を選ぶべきケース
| ユースケース | 理由 |
|---|---|
| Vite / Nuxt 3 / SvelteKit プロジェクト | Vite 設定をそのまま共有でき、エイリアスやプラグインの二重管理が不要 |
| 新規プロジェクト(フレームワーク問わず) | ESM ネイティブ・TypeScript ゼロコンフィグの恩恵が大きい |
| テスト実行速度を改善したい | HMR ベースの Watch モードで開発ループが劇的に速くなる |
| ブラウザ環境での実テストが必要 | @vitest/browser で Playwright 連携が可能 |
✅ Jest を選ぶべきケース
| ユースケース | 理由 |
|---|---|
| 既存の大規模 Jest 資産がある | カスタムマッチャー・設定・CI パイプラインの移行コストが高い |
| Angular / NestJS プロジェクト | CLI がデフォルトで Jest を生成し、公式ドキュメントも Jest 前提 |
| チームに Jest 経験者が多い | 学習コストゼロで即戦力になる |
| サードパーティ連携が必須 | 特定の Jest プラグインに依存している場合 |
🔄 どちらでもよいケース
- React(Next.js 以外)の新規プロジェクト → Vitest 推奨(ただし Jest でも問題なし)
- Next.js → 公式が
@next/jestを提供しているため Jest が安定。ただし Vitest 対応も進んでおり、どちらも選択可能
6. まとめ
Jest = 安定・実績・情報量のデファクト標準
Vitest = 速度・DX・モダン設計の次世代テストランナー
Vitest は Jest の API を意図的に踏襲して設計されているため、「Jest の正統進化版」 と捉えるのが最も正確です。既存プロジェクトで Jest が問題なく動いているなら急いで移行する必要はありません。しかし、新規プロジェクトや Vite エコシステムを採用しているなら、Vitest を選ばない理由を探す方が難しいでしょう。
どちらを選んでも「テストを書く」という本質は変わりません。大切なのはフレームワーク選定に時間をかけすぎず、テストを書き続けることです。