# CI での Vitest vs Jest: 速度、ESM、移行

> CI 向けの Vitest vs Jest: 速度、ESM サポート、設定、移行の手間。どちらの test runner がパイプラインで速く、痛みが少ないか。

Source: https://latchkey.dev/ja/learn/tool-comparisons/vitest-vs-jest  
Updated: 2026-08-20

テストの実行時間はしばしば CI の最も長いステップです - Vitest と Jest は速度と ESM で大きく異なります。

どちらも JS/TS のテストを実行します。Vitest は Vite ネイティブで ESM ファースト、Jest は巨大なエコシステムを持つ定番の標準です。

## Comparison

|  | Jest | Vitest |
| --- | --- | --- |
| ESM サポート | ぎこちない (transform) | ネイティブ |
| 速度 | 良い | 多くの場合より高速 (Vite の transform) |
| 設定 | 成熟、多数のプリセット | 最小限、Vite に整合 |
| エコシステム | 最大 | 成長中、Jest 互換 API |
| 設定 | `vite.config.ts` を再利用 | 独立したJest設定とtransformチェーン |
| アサーション | Jest互換の `expect` を備えたChai | `expect` |
| watchモード | HMR風の再実行 | 標準のwatch |
| カバレッジ | v8またはIstanbul | babel-plugin-istanbulまたはv8 |
| ブラウザモード | あり | なし (jsdom/happy-domによる模擬のみ) |
| ベンチマーク / 型テスト | Tinybench、expect-type | なし |
| シャーディング | あり | あり。v28以降の `--shard` |

## CI では

Vitest は概して高速で、Jest の "Cannot use import statement" 失敗を引き起こす ESM transform の頭痛を回避します。Jest は大規模で成熟したスイートには安全な選択肢のままです。

- Static `import` statements are evaluated before your code runs, so the hoisting `jest.mock()` depends on cannot happen under ESM.
- `require()` of an ESM file with top-level await throws `ERR_REQUIRE_ASYNC_MODULE`.
- The `jest` object has to come from `@jest/globals` or `import.meta.jest` rather than being ambient.
- Jest documentation notes the underlying Node APIs it uses for this are themselves experimental.

```Mocking a module under ESM
# Jest, ESM mode
NODE_OPTIONS="$NODE_OPTIONS --experimental-vm-modules" npx jest

# ...plus transform: {} in config (or a transformer emitting ESM),
# ...plus extensionsToTreatAsEsm for .ts/.jsx,
# ...and jest.mock() no longer works:

const { charge } = await import('./payments.js');
jest.unstable_mockModule('./payments.js', () => ({ charge: jest.fn() }));

# Vitest, no flags, no mode
vi.mock('./payments.js', () => ({ charge: vi.fn() }));
```

> If your codebase is CommonJS and staying CommonJS, none of this applies to you and it is not a reason to migrate. This section is the whole argument for teams that have moved to ESM, and irrelevant to teams that have not.

## どちらも OOM しうる

大きなスイートはどちらの runner でも CI ワーカーを OOM させます - いずれにせよワーカー数を制限し、メモリを適正化してください。

| Approach | Type-checks tests? | Cost |
| --- | --- | --- |
| Jest + babel-jest | No | Fast, but type errors in tests go unnoticed |
| Jest + ts-jest | Yes | Noticeably slower; type-checks on every run |
| Vitest | Via your existing tsconfig | No extra transform config |

> The babel-jest route surprises teams: Jest documentation is explicit that Babel only transpiles and does not type-check, so tests can contain type errors that CI never reports. If you are on babel-jest today, check whether that is a decision you made or one you inherited.

## 移行が見た目より安く済む理由

VitestはJest互換の `expect` とJest互換のモックユーティリティを意図的に実装しています。これにより移行の大部分は書き直しではなく機械的な作業になります。

- `describe`、`it`、`test`、`beforeEach` などは同じです。
- `expect(...)` のmatcherはJest互換なので、アサーションはほとんど変わりません。
- `jest.fn()` は `vi.fn()`、`jest.spyOn()` は `vi.spyOn()`、`jest.mock()` は `vi.mock()` になります。おおむね検索と置換です。
- スナップショットは形式がJest互換なので、既存のスナップショットファイルは通常そのまま使えます。
- Vitestの設定で `globals: true` にすれば `describe`/`it`/`expect` がアンビエントのまま残るので、初日にすべてのテストファイルへimportを追加する必要はありません。

> 引き継げないもの: カスタムのJest transformer、`jest.config.js` のtransformチェーン、そしてJestの内部やJest固有のpluginに依存するもの。作業量が読めないのはここなので、まずこれらを棚卸ししてください。

## 速度について、正直なところ

Vitestは概して高速で、特にwatchモードではViteスタイルの依存グラフを通じて変更分だけを再実行します。コールドなマシンのCIでは差はベンチマークが示すより小さくなります。どちらも実時間の大きな割合を、アサーションではなくインストールとtransformに費やすからです。

- watchモードは差が最も明白で、開発者が毎日体感する場所です。
- どちらもCIマシン間のシャーディングに対応しているため、大規模では並列度が許す水準に両者が収束します。
- `ts-jest` を使っているなら、計測されたJestの遅さの一部は実行ごとの型チェックであって、runnerではありません。Jest自体のせいにする前に `babel-jest` と比較してください。

## Jestに留まるべき場合

- コードベースがCommonJSで、今後もそのままである。移行の主な論拠が当てはまりません。
- カスタムのJest transformerや、Vitestに同等物のないJest固有のpluginに依存している。
- React Nativeを使っており、そこではJestのpresetが踏み固められた道である。
- スイートが大きく安定していて、誰も不満を言っていない。移行は実コストであり、その見返りは開発体験の改善で測られます。

> Jestは衰退していません。v30であり活発に保守されています。「みんなVitestに移っている」はそれ自体では理由になりませんし、動いているテストスイートは今風のスイートより価値があります。

## 移行を安全に進める

1. カスタムtransformer、Jestのplugin、Jestの内部に触れるものを棚卸しします。作業量が読めないのはここだけです。
2. VitestをJestと並べて追加し、`globals: true` を設定して既存のテストファイルを編集不要にします。
3. 1つのディレクトリを変換します。両方のスイートをCIで走らせ、合否の数だけでなく結果そのものを差分で比較します。
4. 変換したファイル全体で `jest.` を `vi.` に検索置換し、きれいに対応しない少数を直します。
5. スナップショットは意図的に再生成し、丸ごと受け入れるのではなく差分をレビューします。
6. 差分が空になり、スプリント1回分まるごと新しいrunnerでスイート全体が緑になってから、Jestを外します。

## The switching cost is mostly in the parts nobody lists

- Assertions and mocks usually port mechanically when the target implements a compatible API; custom transformers and framework plugins do not.
- Snapshot formats differ between runners, so plan to regenerate and review rather than port.
- Run both suites in parallel in CI for a period and diff the results. A migration that changes which tests fail is not a migration, it is a regression you have not found yet.
- Coverage numbers move on a runner change even when the tests do not, because instrumentation differs. Re-baseline any coverage gate deliberately.

## 結論

新規プロジェクトや ESM 中心: Vitest。既存の大きな Jest スイート: ESM の痛みが移行を強いない限り Jest のまま。

## FAQ

### JestからVitestに切り替えるべきですか?

コードベースがESM、あるいはViteビルド上のTypeScriptならはい。JestのESM対応はいまも実験的と文書化されており、Nodeの `--experimental-vm-modules` フラグを要し、`jest.mock()` の代わりに `jest.unstable_mockModule()` を使います。動作しているスイートでCommonJSなら、移行する強い理由はありません。

### JestはESMに対応していますか?

実験的にです。Jestのドキュメントは、実装にバグや機能不足があり得ること、依存する基盤のNode APIもまた実験的であることを述べています。Nodeを `--experimental-vm-modules` で実行し、`transform: {}` を設定するかtransformerからESMを出力し、`.ts` と `.jsx` には `extensionsToTreatAsEsm` を使う必要があります。

### なぜESMで jest.mock() が効かないのですか?

ESMは静的な `import` 文をあなたのコードが動く前に評価するため、`jest.mock()` が依拠する巻き上げが成立しないからです。JestはESM向けに `jest.unstable_mockModule()` を提供しており、対象モジュールの動的 `import()` と組み合わせて使います。

### JestからVitestへの移行はどれくらい大変ですか?

大部分は機械的です。VitestがJest互換の `expect` とJest互換のモックを実装しているためです。`jest.fn()` が `vi.fn()` になるといった具合で、スナップショットは形式互換、`globals: true` により全ファイルの編集を避けられます。読めないのはカスタムのJest transformerとpluginなので、まずそれを棚卸ししてください。

### VitestはJestより実際に速いですか?

概してはい。特にwatchモードで顕著で、変更分だけを再実行します。コールドなマシンのCIでは差が縮まります。どちらも実時間の多くをインストールとtransformに費やすためです。`ts-jest` を使っているなら、Jestのせいにしている一部は実行ごとの型チェックです。

### VitestはViteなしで動きますか?

はい。VitestはViteビルドのないプロジェクトに対しても実行でき、必要な設定を自ら作ります。すでにVite設定がある場合に利点が最大になります。テストとアプリがモジュールを同一に解決するからです。

### JestはTypeScriptのテストを型チェックできますか?

`ts-jest` を使う場合だけです。`babel-jest` 経由は型チェックせずにトランスパイルすると、Jestのドキュメントが明記しており、テスト中の型エラーは黙って通ります。テストスイートが型チェックされていると思い込んでいるチームが、実はされていないと気づく落とし穴です。

### Jestは非推奨ですか?

いいえ。Jestはバージョン30.4で活発に保守されています。Vitestは特にViteのエコシステムで強い勢いがありますが、「みんな移っている」は技術的な理由ではなく、動いているスイートは流行のスイートより価値があります。

---

Latchkey runs CI/CD that repairs its own failures. Agent entry points: https://latchkey.dev/agent.txt, https://latchkey.dev/openapi.json, https://latchkey.dev/llms.txt
