目次
この記事が解決する悩み
- Jest で書いた ESM のテストが「Must use import to load ES Module」で起動しない
- Vitest に移したいけれど、どこで何が壊れるのか先に知りたい
- Vitest 5 に上げたとたん、モックの呼び出し回数を数えていたテストが落ちた
package.json の type: module と Jest の基本的な使い方を前提にします。
結論
書き換えるのは3箇所です。@jest/globalsの import をvitestに変え、describe/it/expectも明示的に import するjest.mockをvi.mockに変え、default export を模すときは{ default: ... }の形で返す- jsdom を使うファイルの docblock を
// @vitest-environment jsdomにする
// 移行前(Jest 30 + ESM)
import { jest } from "@jest/globals";
jest.unstable_mockModule("./greeting.js", () => ({ default: () => "モック" }));
const { default: greeting } = await import("./greeting.js");
// 移行後(Vitest 5)
import { expect, test, vi } from "vitest";
import greeting from "./greeting.js";
vi.mock("./greeting.js", () => ({ default: () => "モック" }));
await import は、静的な import に戻せます。移行後は 7 テストが 1 秒未満で通りました。
移行前の状態
題材は ESM で書いた小さな買い物カートのプロジェクトです。type: module の package.json で、テストは Jest 30.5.2 に動かしてもらっていました。
素の jest を実行すると、テストファイルの読み込みで止まります。
$ npx jest jest/tests
FAIL jest/tests/cart.test.js
● Test suite failed to run
Must use import to load ES Module: 実行ディレクトリ/jest/tests/cart.test.js
- Use Node v24.9+ where Jest supports require(esm) natively
--experimental-vm-modules を付けるかの2つです。後者で動かすと、今度はトップレベルの jest.mock が落ちました。
$ NODE_OPTIONS=--experimental-vm-modules npx jest jest/mock-broken.test.js
● Test suite failed to run
ReferenceError: require is not defined
at require (jest/mock-broken.test.js:5:5)
at _getJestObj (jest/mock-broken.test.js:3:1)
jest.mock が require を経由するため、モジュールの差し替えができません。回避策は jest.unstable_mockModule と await import の組み合わせです。
import { jest } from "@jest/globals";
jest.unstable_mockModule("../../src/postoffice.js", () => ({
sendReceipt: jest.fn(async (customer) => ({ id: `mock-${customer.id}`, amount: 0 })),
}));
// モックした後に読み込む必要がある
const { subscribe } = await import("../../src/subscribe.js");
// 実行結果: Test Suites: 5 passed, 5 total / Tests: 6 passed, 6 total
jest-environment-jsdom パッケージの追加も要りました。
Vitest 5 に置き換えると何が壊れるか
同じファイルを Vitest 5.0.1 で実行すると、Jest のグローバル前提のコードは読み込み時点で落ちます。$ npx vitest run jest/tests/cart.test.js
FAIL jest/tests/cart.test.js
ReferenceError: describe is not defined
❯ jest/tests/cart.test.js:3:1
globals は既定で無効ですよ。@jest/globals を import していたファイルは、さらに手前で止まります。
$ npx vitest run jest/tests/subscribe.test.js
Error: Do not import `@jest/globals` outside of the Jest test environment
❯ node_modules/@jest/globals/build/index.js:23:7
@jest-environment jsdom は Vitest 5 でもそのまま読まれます。docblock を外すと document が無いと言われます。
$ npx vitest run broken/dom-no-docblock.test.js
ReferenceError: document is not defined
❯ broken/dom-no-docblock.test.js:9:3
実際に書き換えたところ
モックのファクトリの形が変わります。Jest は返り値そのものが default export でしたが、Vitest は export を並べたオブジェクトを要求します。import { expect, test, vi } from "vitest";
import greeting from "../../src/greeting.js";
vi.mock("../../src/greeting.js", () => ({
default: () => "こんにちは、モックさん",
}));
test("モックに差し替わる", () => {
expect(greeting("ゆき")).toBe("こんにちは、モックさん");
});
vi.mock は静的な import より前に巻き上げられるので、await import を書く順番を気にしなくて済みます。6ファイル・7テストをまとめて流した結果です。
$ npx vitest run vitest/tests
Test Files 1 failed | 5 passed (6)
Tests 1 failed | 6 passed (7)
Vitest 5 で変わったモックの既定値
Vitest 5 からclearMocks の既定が true になりました。テストごとに vi.clearAllMocks() が呼ばれるので、モックの呼び出し履歴はテストをまたいで残りません。
import { expect, test, vi } from "vitest";
const notify = vi.fn();
test("1回目", () => {
notify();
expect(notify).toHaveBeenCalledTimes(1);
});
test("2回目", () => {
notify();
expect(notify).toHaveBeenCalledTimes(2);
});
// 実行結果
// AssertionError: expected "vi.fn()" to be called 2 times, but got 1 times
// ❯ vitest/tests/call-history.test.js:14:18
clearMocks: false を書けば旧挙動に戻せます。
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
clearMocks: false,
},
});
// 実行結果: Test Files 6 passed (6) / Tests 7 passed (7)
つまずきポイント
-tの区切り文字が空白から>に変わった。CI で-t "math adds"を渡していたテストは skipped になり、-t "math > adds"で当たる- Jest 側で
unstable_mockModuleを使っていたファイルは、Vitest に移すとvi.mockで普通に書ける(ESM でも動く) - jsdom は
jest-environment-jsdomではなくjsdomを入れて、environment を指定する形になる
検証環境
| OS | Windows 11 (build 10.0.26200) |
|---|---|
| CPU | AMD Ryzen 9 PRO 8945HS w/ Radeon 780M Graphics |
| メモリ | 28GB |
| 言語/ツール | Node.js 22.23.2(ホスト既存)/ jest 30.5.2 と jest-environment-jsdom 30.5.2(移行前)/ vitest 5.0.1 と vite 8.3.0 / jsdom(npm で一時ディレクトリ内に install) |
| 使用コマンド | jest jest/tests(ESM のまま実行・experimental-vm-modules フラグ あり/なし)/ vitest run(移行前のファイル・移行後のファイル・clearMocks: false の config)/ vitest run -t(区切り文字の比較) |
| 測定日 | 2026-09-22 |

まとめ
Jest で ESM を回すために積んでいた設定は、Vitest では要りませんでした。| 項目 | Jest 30.5.2 | Vitest 5.0.1 |
|---|---|---|
| ESM のテスト実行 | --experimental-vm-modules が必要(Node 22) |
そのまま動く |
| モジュールの差し替え | unstable_mockModule + await import |
vi.mock(巻き上げあり) |
| グローバル API | 既定で有効 | 既定で無効(明示 import) |
| jsdom | jest-environment-jsdom を追加 |
jsdom を追加して environment を指定 |
| モックの履歴 | テストをまたいで残る | テストごとにクリア(clearMocks が既定 true) |
clearMocks の既定変更だけで、テストの書き方そのものは Jest のままで動きますよ。
参考
本文の記述は以下の一次情報を確認して書いています。- Migrating to Vitest 5.0(公式マイグレーションガイド)
- Vitest 5.0 is out!(公式ブログ)
- Migrating from Jest(公式ガイド)
- Vitest v5.0.0 リリースノート(GitHub)
- clearMocks(設定リファレンス)
- ECMAScript Modules(Jest 公式ドキュメント)
あわせて読みたい
- 【Node.js】Node 26 完全ガイド2026 — Temporal API と Iterator.concat
- 【Node.js】ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX の直し方
- 【TypeScript】TypeScript 6.0 移行ガイド — 5.x からの手順と新機能
- 【Vite】Vite 8 移行ガイド2026 — Rolldown 統合で壊れる設定
【広告】お名前.comならドメイン取得が格安。![]()
【広告】このサイトはConoHa WINGで運営しています。安定した高速サーバーで快適にブログを書けています。いつもありがとう!!![]()

コメント