2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[エラー対応] Vitest の vi.mock で ‘Cannot access before initialization’ が出たときの解決法

Posted at

はじめに

Vitestでテストを作成中にエラーが発生したので、その解消方法を備忘録としてまとめます。
同じエラーに遭遇した方の参考になれば幸いです。

発生したエラーメッセージ

Error: [vitest] There was an error when mocking a module. If you are using "vi.mock" factory, make sure there are no top level variables inside, since this call is hoisted to top of the file. Read more: https://vitest.dev/api/vi.html#vi-mock

Caused by: ReferenceError: Cannot access 'supabaseMock' before initialization

エラー発生時のソースコード

login.spec.tsx
import { render,screen } from "@testing-library/react";
import { MemoryRouter } from "react-router-dom";
import { Login } from "../pages/Login";
import userEvent from "@testing-library/user-event";


// useNavigateのモック化(一部モック化)
const mockedNavigator = vi.fn();
vi.mock("react-router-dom", async () => {
    const actual = await vi.importActual<typeof import("react-router-dom")>("react-router-dom");
    return {
        ...actual,
        useNavigate: () => mockedNavigator,
    };
});
// supabaseのモック化
const supabaseMock = {
    auth: {
        getSession: vi.fn(),
        onAuthStateChange: vi.fn(() => ({
            data: { subscription: { unsubscribe: vi.fn() } },
        })),
        signOut: vi.fn(),
    },
}
vi.mock("../utils/supabase", () => ({
    supabase: supabaseMock,
}));


// fetchUserのモック作成
const fetchUserMock = vi.fn();
vi.mock("../service/fetchUser", () => ({
    fetchUser: fetchUserMock
}));

// テストで使うユーザー操作イベントを定義
const user = userEvent.setup();


describe("初期表示", () => {
    // セッションの初期化
    beforeEach(() => {
        supabaseMock.auth.getSession.mockResolvedValue({
            data: { session: null }, // ログインしていないデフォルト
        });
    });
    test("ユーザーが Supabase の users テーブルに存在する場合、検索画面に遷移すること", async () => {
        // ログインユーザとユーザを取得
        supabaseMock.auth.getSession.mockResolvedValue({
            data: { session: { user: { id: "user-123" } } },
        });
        fetchUserMock.mockResolvedValue({ id: "user-123" });

        render(
            <MemoryRouter>
                <Login />
            </MemoryRouter >
        );
        const loginButton = await screen.findByRole("button", { name: "Googleでログイン" })
        await user.click(loginButton);
        expect(mockedNavigator).toHaveBeenCalledWith("/substitute-search")

    })

});

原因

Vitestでは vi.mock が ファイル冒頭に強制的に移動(ホイスティング) されます。
そのため、モック化に使う変数がまだ定義されていない状態で参照されてしまい、Cannot access before initialization が発生します。

処理の流れとしては以下のようになります。
1. Vitest がファイルを読み込む
2. vi.mock が強制的に先頭へ移動する(ホイスティング)
3. まだ定義されていない supabaseMock を参照してしまう

解決策

vi.hoisted を使用する
vi.hoisted はトップレベル変数を「安全に」定義するための仕組みです。
これを使うことで、vi.mock 内から参照する変数も正しく初期化されます。

login.spec.tsx
// supabase,fetchUserのモック化
const { supabaseMock, fetchUserMock } = vi.hoisted(() => ({
   supabaseMock: {
       auth: {
           getSession: vi.fn(),
           onAuthStateChange: vi.fn(() => ({
               data: { subscription: { unsubscribe: vi.fn() } },
           })),
           signOut: vi.fn(),
       },
   },
   fetchUserMock: vi.fn(),
}));
vi.mock("../utils/supabase", () => ({
   supabase: supabaseMock,
}));

vi.mock("../service/fetchUser", () => ({
   fetchUser: fetchUserMock
}));

終わりに

今回のエラーは、vi.mock が強制的にホイスティングされる仕組みを理解していなかったことが原因でした。
vi.hoisted を使えばモック変数を安全に定義できるので、Vitestでモックを扱う際には覚えておきたいポイントです。

自分自身もまだVitestの理解が十分ではないので、引き続き学習を進めていきます。

参考

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?