Vitest vi.mockのホイスティング挙動とvi.hoistedによる変数参照の落とし穴
Vitest Testing TypeScript Frontend
結論
モック外の変数を参照するには vi.hoisted() で変数自体もホイストさせます。
// Vitest公式仕様:vi.hoisted() を用いた安全なモック記述
import { vi, describe, it, expect } from 'vitest';
// ⭕ vi.hoisted で変数をファイル最先頭へホイスト宣言
const { mockUserData } = vi.hoisted(() => {
return { mockUserData: { id: 1, name: '山田太郎' } };
});
// vi.mock も最先頭へホイストされるため、上の mockUserData が安全に参照できる
vi.mock('./userService', () => {
return {
getUser: vi.fn(() => mockUserData),
};
});
import { getUser } from './userService';
describe('ユーザー取得テスト', () => {
it('モックデータが正常に返ること', () => {
expect(getUser()).toEqual({ id: 1, name: '山田太郎' });
});
});
Vitest公式仕様:vi.mock の自動ホイスティング
Vitest公式ドキュメント(vitest.dev/guide/mocking.html)の規定通り、vi.mock はトランスパイル時に ファイル内の全 import 文や変数宣言よりも一番上(最先頭)へ自動的に移動(Hoist) して実行される仕様になっています。
実際に起こる障害:ReferenceError: Cannot access before initialization 事故
テストコードを書く際、トップレベルで定義したオブジェクトを vi.mock のファクトリー関数内で参照してしまうミスが頻発します。
// ❌ 事故:Cannot access 'mockUser' before initialization でテストが即死する例
import { vi } from 'vitest';
const mockUser = { name: 'テスト' };
// ❌ vi.mock は上の mockUser 定義よりも「前」に移動して実行されるため、mockUser は undefined
vi.mock('./api', () => {
return { fetchUser: () => mockUser };
});
vi.mock は実行タイミングが繰り上げられるため、クロージャ外の変数を直接参照すると ReferenceError を投げてテスト実行が完全失敗 します。
解決手順
- モック内で外の変数を参照したい場合は、
const { myVar } = vi.hoisted(() => ({ myVar: ... }))関数を使用する vi.hoistedはvi.mockよりも前に評価されるため、安全に値を共有・利用できる- ファイル間で共通のモックデータを使う場合は、
__mocks__ディレクトリや専用のセットアップファイルへ切り出す