できない.dev

Vitest の vi.mock が効かない(巻き上げと factory のスコープ)

vi.mock はファイル先頭へ巻き上げられて import より前に実行されるため、外側スコープの変数を factory 内で参照すると失敗する。
vi.hoisted を使うか、巻き上げない vi.doMock に切り替えると解決する。

#vitest#vi-mock#mock#hoisting#test

公開:

要約

Vitest の vi.mock は呼び出し位置に関係なく、トランスフォーム時にファイルの先頭(すべての import より上)へ巻き上げられます。

そのため factory 関数の中でモジュールスコープの変数を参照すると、宣言前アクセスになって失敗します。

「モックしたはずなのに本物が呼ばれる」「Cannot access ... before initialization」が典型症状です。

解決策は、factory で使う値を vi.hoisted で一緒に巻き上げるか、巻き上げない vi.doMock に切り替えることです。

よくある原因

  1. vi.mock が import より上へ移動するため、下に書いた変数を factory が参照できない。
  2. factory が import 解決時(変数宣言より前)に実行される。
  3. モックのパスが実際の import と一致していない。

解決策

1. vi.hoisted で変数ごと巻き上げる

import { vi, test, expect } from "vitest";
 
const { sendMock } = vi.hoisted(() => ({ sendMock: vi.fn() }));
 
vi.mock("./mailer", () => ({ send: sendMock }));

vi.hoisted の戻り値は vi.mock と同じ高さへ巻き上がるので、factory から安全に参照できます。

2. 巻き上げたくないなら vi.doMock

let impl = () => "real";
vi.doMock("./config", () => ({ get: () => impl() }));
const target = await import("./target");

vi.doMock は先頭へ移動しないため、直前に定義した変数を参照できます。
ただしモック登録より前に読み込み済みのモジュールには効きません。

3. パスを import と完全一致させる

エイリアス(@/)と相対パスが混在すると別モジュール扱いになります。vi.mock の引数は対象の import 文と同じ表記にそろえます。

詳しくは公式の vi.mock / vi.hoisted を参照してください。

この記事は役立ちましたか?