はじめに
Jestでテストを実行した際に「ReferenceError: fetch is not defined」というエラーが発生しました。これはSupabaseのモックが正しく適用されず、本物のSupabaseクライアントが呼ばれてしまったことが原因です。この記事では、エラーの原因と解決方法を記録します。
エラー内容
エラーメッセージ
ReferenceError: fetch is not defined
at /Users/.../node_modules/@supabase/supabase-js/src/lib/fetch.ts:7:42
at fetch (/Users/.../node_modules/@supabase/supabase-js/src/lib/fetch.ts:34:12)
詳細
エラーの原因は、Jestのテスト環境(Node.js)にはブラウザのfetch APIがグローバルに存在しないためです。本来はSupabaseをモックして実際のAPI呼び出しを避けるべきですが、モックが正しく適用されていませんでした。
実際に起きていたこと:
テスト実行
↓
App コンポーネントをレンダリング
↓
本物の supabase.js がインポートされる(モックが適用されていない)
↓
Supabaseクライアントが fetch を呼び出そうとする
↓
Node.js環境には fetch がない → エラー
根本原因:モックが適用されなかった3つの理由
1. __mocks__ フォルダの配置場所が間違っていた
Jestの自動モック機能を使う場合、__mocks__ フォルダはモック対象のモジュールと同じディレクトリに配置する必要がある。
❌ 間違った配置
src/
├── supabase.js
└── test/
└── __mocks__/
└── supabase.js ← Jestはここを見ない
✅ 正しい配置
src/
├── __mocks__/
│ └── supabase.js ← src/supabase.js のモックはここ
└── supabase.js
2. jest.mock() の巻き上げ(hoisting)問題
jest.mock() はJestによって自動的にファイルの先頭に移動される。そのため、外部からインポートした変数を参照できない。
// ❌ 動かない例
import mockSupabase from "./__mocks__/supabase.js";
jest.mock("../supabase", () => mockSupabase); // mockSupabase は undefined
// ↑ 実際の実行順序はこうなる:
// 1. jest.mock() が先に実行される(mockSupabase はまだ undefined)
// 2. import が実行される
エラー解決
解決方法1:__mocks__ フォルダを正しい場所に移動
-
src/test/__mocks__/supabase.jsをsrc/__mocks__/supabase.jsに移動 - テストファイルで
jest.mock()を呼ぶ
// sample.spec.js
import App from "../index.jsx";
import '@testing-library/jest-dom';
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
jest.mock("../supabase"); // これだけで src/__mocks__/supabase.js が使われる
describe("学習記録アプリ", () => {
// ...
});
なぜ jest.mock() だけでモックできるのか?
jest.mock("../supabase") を書くと、Jestは以下の処理を自動的に行う:
-
"../supabase"のパスを解決 →src/supabase.js - そのディレクトリに
__mocks__フォルダがあるか探す →src/__mocks__/ - 同じ名前のファイルがあるか探す →
src/__mocks__/supabase.jsが見つかる - テスト実行時、
"../supabase"のインポートを自動的にsrc/__mocks__/supabase.jsに置き換える
テストファイルでモックをimportしなくていい理由は、Appコンポーネントが内部でsupabaseをインポートしている部分をJestが横取りしてモックに差し替えるから。
テストファイル Appコンポーネント
│ │
│ jest.mock("../supabase") │ import supabase from "./supabase"
│ │ │ │
│ ▼ │ ▼
│ Jestが記憶 │ Jestが介入
│ │ │
│ │ ▼
│ src/__mocks__/supabase.js を返す
解決方法2:jest.mock() 内でモックを直接定義
__mocks__ フォルダを使わず、テストファイル内で完結させる方法。
jest.mock("../supabase", () => {
const mockRecords = [
{ id: 1, title: "test", time: 3 }
];
const mockQueryBuilder = {
select: jest.fn(() => Promise.resolve({ data: mockRecords, error: null })),
insert: jest.fn((newRecord) => ({
select: jest.fn(() => Promise.resolve({
data: { ...newRecord, id: mockRecords.length + 1 },
error: null
}))
})),
delete: jest.fn(() => ({
eq: jest.fn(() => Promise.resolve({ error: null }))
}))
};
return {
__esModule: true,
default: {
from: jest.fn(() => mockQueryBuilder)
}
};
});
比較表
| 方法 | メリット | デメリット |
|---|---|---|
__mocks__ フォルダを使う |
モックを別ファイルに分離できる、複数のテストで再利用しやすい | 配置場所のルールを理解する必要がある |
jest.mock() 内で定義 |
テストファイル内で完結、わかりやすい | テストファイルが長くなる、再利用しにくい |
おわりに
「fetch is not defined」エラーは、一見Node.js環境の問題に見えますが、実際はモックが正しく適用されていないことを示すサインでした。
学び:
- Jestの
__mocks__フォルダは、モック対象と同じディレクトリに配置する -
jest.mock()は巻き上げられる(hoist)ため、外部変数を参照できない - エラーメッセージの「場所」を見ると、本物のライブラリが呼ばれているかどうかがわかる