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 を投げてテスト実行が完全失敗 します。


解決手順

  1. モック内で外の変数を参照したい場合は、const { myVar } = vi.hoisted(() => ({ myVar: ... })) 関数を使用する
  2. vi.hoistedvi.mock よりも前に評価されるため、安全に値を共有・利用できる
  3. ファイル間で共通のモックデータを使う場合は、__mocks__ ディレクトリや専用のセットアップファイルへ切り出す