2026年9月3日にリリースされた Vitest 5.0 は、パフォーマンスを重視したメジャーリリースです。Node.js 22.12.0 以降と Vite 6.4.0 以降が必須になりました。また、モックのクリアがデフォルトで有効になり、await されていない非同期アサーションは失敗するようになり、レポーターの出力先は単一の .vitest/ ディレクトリ配下にまとめられました。
アップグレード後に発生する失敗の多くは簡単に対処できます。厄介なのは、Vitest 4 ではローカルで通っていたテストが CI で失敗し、しかもエラーが原因を何も示さないケースです。
本記事では、v5.0.0 のリリースノートの内容を影響度順に整理します。まず実際に何が高速化されたのかを確認します。続いて、コード修正が必要な変更、パスやマッチングルールが気づかないうちに変わる変更を取り上げ、最後にアップグレードのチェックリストと結論を示します。
重要なポイント
- Vitest 5.0 には Node.js 22.12.0 以降と Vite 6.4.0 以降が必要です。
- Vitest チームのベンチマークでは、検証した構成の大半で 8〜25% 高速化し、一部の VM プール構成では最大 53% 高速化しています。環境セットアップが処理時間の大半を占める構成では、ほとんど変わりません。
clearMocksのデフォルトがtrueになりました。そのため、セットアップファイル、beforeAllフック、または前のテストで記録された呼び出し履歴に対するアサーションでは、呼び出し回数が 0 になります。- Blob レポート、アタッチメント、および JSON・JUnit・HTML レポーターの出力は、デフォルトで
.vitest/配下に出力されます。CI のアーティファクト関連ステップの更新が必要です。 toThrow('')はスローされたあらゆるエラーにマッチするようになりました。空のメッセージを検証したい場合は、明示的なパターンを指定する必要があります。
Vitest 5 はなぜ高速化したのか
Vitest 5 のアナウンスによると、Vitest チーム自身のベンチマークでは、大半の構成で 8〜25% 高速化しています。最も効果が大きいのは VM プールで、一部の構成では最大 53% 高速化しました。ただし、すべての構成が速くなるわけではありません。jsdom と分離(isolation)を組み合わせた forks のように、テスト環境の作成が処理時間の大半を占める実行では、Vitest 4.1 との差は 3% 以内にとどまります。これらの数値は vitest-dev/benchmarks によるものです。チームは、5 ファイルの小規模パッケージから 1,280 モジュールのモノリスまで、さまざまな規模のテスト用アプリを生成して計測しました。VoidZero のローンチ投稿では、これを丸めて「vm pools up to 53% faster, ~18% boost across the board including Browser Mode」(VM プールは最大 53% 高速化、Browser Mode を含め全体で約 18% 向上)と表現しています。
リリースノートによると、高速化の大部分は次の 4 つの変更によるものです。
- Vite サーバーの共有: インラインプロジェクトは、それぞれ個別に Vite サーバーを起動するのではなく、1 つの Vite サーバーを共有するようになりました。
fsModuleCache: トップレベルのオプションになりました。変換済みのモジュールをディスクに保存するため、再実行時や別の Vitest プロセスでは変換処理を省略できます。- ラウンドトリップの削減: 変換済みのモジュールは、メインプロセスから 1 回の通信でワーカーに届くようになりました。
- VM プールでの再利用:
vmThreadsプールとvmForksプールは、コンパイル済みのコードをコンテキスト間で共有し、モジュールグラフを事前に読み込みます。
Vitest は Node のオンディスクコンパイルキャッシュにも対応していますが、こちらはオプトイン方式です。
コード修正が必要な Vitest 5 の変更
Vitest 5 には、初回実行時にテストの失敗や設定エラーを引き起こす変更が 6 つあります。それぞれについては移行ガイドで説明されています。
| 変更内容 | 初回実行時の症状 | 対処法 |
|---|---|---|
clearMocks: true がデフォルトに | 呼び出し回数のアサーションが 0 になる | アサーションを行うテスト内で呼び出しを発生させる、または clearMocks: false を設定する |
| await されていない非同期アサーション | テストが失敗する | await を追加する |
トップレベル以外での巻き上げ対象 vi 呼び出し | エラーがスローされる | モジュールスコープに移動する |
sequential の削除 | API が削除されている | { concurrent: false } を使う |
| 親ディレクトリの設定ファイルを探索しない | 設定ファイルが見つからない | パッケージのフォルダーに設定ファイルを追加する |
| Bench API の刷新 | 既存のベンチマークコードが動かない | フィクスチャモデルに移行する |
モックのクリアと巻き上げ
Vitest 5 では clearMocks のデフォルトが true になったため、各テストの前に vi.clearAllMocks() が実行されます。セットアップファイル、beforeAll フック、または前のテストで記録された呼び出し履歴は、次のテストがアサーションを行う前に消去されます。モックの実装はそのまま残ります。
// Vitest 5.0.x
const track = vi.fn()
beforeAll(() => initAnalytics(track))
it('tracks once on init', () => {
expect(track).toHaveBeenCalledTimes(1) // now receives 0 calls
})
対処法は、アサーションを行うテストの中で呼び出しを発生させることです。テストスイートを見直す間は、test 設定で clearMocks: false を指定すれば従来の挙動に戻せます。
vi.mock などの巻き上げ(hoisting)対象の vi 呼び出しを、ファイルのトップレベル以外で使用するとエラーがスローされるようになりました。もともと Vitest はこれらの呼び出しをモジュールの先頭に巻き上げるため、describe ブロック内に書いたコードは、書かれた位置では実行されていませんでした。
// Vitest 5.0.x: throws
describe('UserCard', () => {
const fetchUser = vi.fn()
vi.mock('./api', () => ({ fetchUser }))
})
// Vitest 5.0.x: works
const { fetchUser } = vi.hoisted(() => ({ fetchUser: vi.fn() }))
vi.mock('./api', () => ({ fetchUser }))
describe('UserCard', () => {
it('renders the user', async () => {
fetchUser.mockResolvedValue({ name: 'Ada' })
// mount and assert
})
})
Vue のテストスイートで API レイヤーをモックしている場合も、同じトップレベルのパターンが適用されます。詳しくは Vitest を使った Vue テストでの API 呼び出しのモックを参照してください。
await されていないアサーション
非同期アサーションを await せずに残したテストは失敗するようになりました。expect(...).resolves や .rejects の前に await がないと、テストは失敗します。
// Vitest 5.0.x
test('loads config', async () => {
expect(loadConfig()).resolves.toEqual({ ok: true }) // fails
await expect(loadConfig()).resolves.toEqual({ ok: true }) // passes
})
次の grep で候補を一覧できます。ただし、各ヒット箇所の先頭に await があるかどうかは個別に確認する必要があります。
grep -rnE "expect\(.*\)\.(resolves|rejects)" src/ --include='*.test.*' --include='*.spec.*'
並行実行、設定ファイルの探索、ベンチマーク
テストとスイートの sequential オプションは削除されました。並行実行を無効にするには、test('example', { concurrent: false }, ...) または describe('suite', { concurrent: false }, ...) を使用します。
Vitest 5 は、現在のフォルダーより上位のフォルダーで設定ファイルを探さなくなりました。パッケージのサブフォルダーから vitest を実行する場合は、そのフォルダーに専用の設定ファイルが必要です。
Bench API は刷新されました。ファイルの先頭で bench をインポートする必要はなくなり、代わりにベンチマークファイル内の通常の test() 呼び出しの中で、テストコンテキストから取得します。
リリースノートには、該当する場合に確認すべき破壊的変更がほかにも挙げられています。
expect.pollはタイムアウト時に失敗するようになりました。- 非推奨だったエントリーポイントが削除されました。
@vitest/runnerは非推奨になりました。また、アサーションのコードがvitest本体に同梱されるようになったため、vitestは@vitest/expectに依存しなくなりました。@vitest/browser-webdriverioプロバイダーは vitest-community オーガニゼーションに移管され、今後はコミュニティによってメンテナンスされます。workerIdは 1 始まりになりました。
toThrow('') はスローされたあらゆるエラーにマッチするようになりました。本当に空のメッセージを検証したい場合は、代わりに /^$/ のような正規表現を渡してください。
Vitest 5 で気づかないうちに変わる挙動
Vitest 5 には、エラーをスローしない変更も 6 つあります。これらの変更では、既存の構成のまま、パス、フィルター、マッチング結果が裏側で変わります。
- 出力パス: Blob レポートと
--merge-reportsのデフォルトは.vitest/blob/になりました。アタッチメントは.vitest-attachements/から.vitest/attachments/に移動しました。JSON・JUnit・HTML レポーターのファイルも、デフォルトで.vitest配下に出力されます。 -tフィルター: テスト名フィルターの区切り文字が>になりました。スイートのパスでフィルタリングしている CI スクリプトを確認してください。- ブラウザーのロケーター: Browser Mode では
locators.exactがデフォルトで有効になりました。 - テキストのマッチング:
toHaveTextContentは厳密な比較を行うようになりました。新たな代替手段としてtoMatchTextContentが追加されています。 - カバレッジの glob:
includeとexcludeのパターンは、プロジェクトルートからの相対パスでファイルにマッチするようになりました。また、ワイルドカードを含まないパターンはフォルダー全体として扱われます。カバレッジの集計対象となるファイルが変わる可能性があるため、初回実行後にしきい値を確認してください。 - インラインプロジェクト: インラインプロジェクトは、
extends: trueが設定されている場合と同様に、ルートの設定を継承するようになりました。
一般的なアーティファクトのステップは、次のように変更します。
- path: .vitest-attachements/
+ path: .vitest/attachments/
+ # sharded runs: upload .vitest/blob/ for --merge-reports
Vitest 5 の新機能で押さえておきたいもの
vi.when を使うと、スパイに対して引数の組み合わせごとに異なる結果を返させることができます。calledWith は非対称マッチャー(asymmetric matcher)を受け付け、どの条件にもマッチしない引数での呼び出しは元の実装にフォールスルーします。
// Vitest 5.0.x
vi.when(getUser).calledWith(1).thenResolve({ id: 1, name: 'Ada' })
Browser Mode では、test.browser.traceView: true を設定するとトレースビューが有効になります。各インタラクション、アサーション、page.mark の呼び出しが DOM スナップショットとして保存されるため、UI 上でテストを 1 ステップずつ再生できます。
ネストされたプロジェクトもサポートされるようになり、モノレポで関連するプロジェクトをグループ化しやすくなりました。
Vitest 5 アップグレードのチェックリスト
- CI とローカル環境を Node.js 22.12.0 以降、Vite 6.4.0 以降に移行する。
- 前述の grep を実行し、
awaitが抜けている箇所にすべて追加する。 - すべての
vi.mockとvi.hoistedの呼び出しを、各ファイルのトップレベルに移動する。 sequentialを{ concurrent: false }に置き換え、親ディレクトリの設定ファイルに依存していたパッケージのフォルダーに設定ファイルを追加する。- テストスイートを実行する。呼び出し回数のアサーションが失敗した場合は修正するか、一時的な措置として
clearMocks: falseを設定する。 - CI のアーティファクトのパスを
.vitest/に更新し、-tフィルターとカバレッジのしきい値を見直す。
Vitest 5 には今すぐアップグレードすべきか
CI がすでに Node.js 22.12.0 以降と Vite 6.4.0 以降で動いているなら、今スプリント中に Vitest 5 へアップグレードしましょう。必要な修正の大半は機械的に行えるものです。
例外は、テストをまたいで引き継がれるモックの呼び出し履歴に対してアサーションを行っているスイートです。セットアップファイルや beforeAll フックで記録された履歴だけでなく、あるテストが別のテストの呼び出しに依存しているケースも含みます。こうした失敗は原因の手がかりをまったく示さないため、先にこれらのアサーションを見直してからアップグレードしてください。また、コンポーネントのテストスイートでは、テキストとロケーターのアサーションも再実行して確認しましょう。これらのアサーションがどこに現れやすいかは、Vitest による Svelte 5 コンポーネントのテストで紹介しているパターンが参考になります。
Vitest 5 は高速になっており、破壊的変更で動かなくなるものの大半は、もともと誤っていたテストコードです。まずはブランチ上で grep の実行と vi.mock の移動を行い、CI のアーティファクトのパスを確認してください。残りの問題は、最初の CI 実行が教えてくれます。
よくある質問
Vitest 5 では mockReset や restoreMocks もデフォルトで有効になりますか?
いいえ。Vitest 5 の移行ガイドでデフォルトが変更されたのは clearMocks だけです。clearMocks は各テストの前に vi.clearAllMocks() を呼び出し、mock.calls、mock.instances、mock.contexts、mock.results をリセットしますが、実装はそのまま残します。mockReset はさらに踏み込み、履歴を消去したうえで各実装を元の状態に戻します。そのため、vi.fn(impl) で作成したモックは impl に戻ります。restoreMocks は、vi.spyOn で作成したスパイの元の実装を復元します。
Vitest 5 にアップグレードした後、-t フィルターにマッチするテストが減ったのはなぜですか?
Vitest 5 では、testNamePattern(-t フラグ)は完全なテスト名に対して照合されます。完全なテスト名は、各スイート名とテスト名の間に ' > ' を挟んで組み立てられ、レポーターの出力に表示されるものと同じ文字列です。Vitest 4 では、Jest と同様に各要素の間を半角スペース 1 つで区切っていました。パターンが壊れるのは、名前のある要素から次の要素にまたがってマッチさせている場合だけです。修正するには、-t adds のように 1 つの要素だけにマッチさせるか、-t 'math.*adds' のように要素の間にワイルドカードを入れてください。
Yarn でアップグレードした後、Vitest 5 が vite を解決できないのはなぜですか?
Vitest 5 では、vite が直接の依存関係から必須の peer dependency に変わりました。そのため、Vitest はプロジェクトにインストールされている Vite のバージョン上で動作します。npm、pnpm、Bun、Deno は peer dependency を自動で追加しますが、Yarn ではその作業を自分で行う必要があります。package.json にバージョン 6.4.0 以降の vite を追加して再インストールすれば、Vitest は再び vite を解決できるようになります。
Vitest 5 でシャード化したテストレポートをマージするにはどうすればよいですか?
各シャードを blob レポーターで実行します。たとえば 1 台目のマシンでは vitest run --reporter=blob --shard=1/3 を実行します。各シャードはデフォルトで結果を .vitest/blob/ に書き出し、出力先は --outputFile.blob フラグで変更できます。すべてのマシンからこのディレクトリを 1 つの最終ジョブにコピーし、vitest --merge-reports を実行してください。テストがアタッチメントをファイルとして保存している場合は、attachments フォルダーもマージ用のジョブに持ち込んでください。
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.
Star on GitHub12k