概要
fetch はブラウザ / Node.js のグローバル関数であり、Jest テスト内では jest.spyOn(global, "fetch") でモック化できる。成功レスポンス・HTTP エラー・ネットワーク障害の 3 ケースをテストすることで、クライアント側データ取得関数の堅牢性を検証する。
インストール
npm install --save-dev jest @types/jest ts-jest
実装
テスト対象の取得関数
// lib/api.ts
export type User = {
id: number;
name: string;
email: string;
};
export async function fetchUser(id: number): Promise<User> {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) {
throw new Error(`HTTP error: ${res.status}`);
}
return res.json() as Promise<User>;
}
export async function fetchUsers(): Promise<User[]> {
const res = await fetch("/api/users");
if (!res.ok) {
throw new Error(`HTTP error: ${res.status}`);
}
const data = (await res.json()) as { users: User[] };
return data.users;
}
fetch モックテスト
// lib/api.test.ts
import { fetchUser, fetchUsers } from "./api";
// fetch レスポンスを生成するヘルパー
function mockResponse(body: unknown, status = 200): Response {
return {
ok: status >= 200 && status < 300,
status,
json: () => Promise.resolve(body),
} as Response;
}
describe("fetchUser", () => {
let fetchSpy: jest.SpyInstance;
beforeEach(() => {
fetchSpy = jest.spyOn(global, "fetch");
});
afterEach(() => {
fetchSpy.mockRestore();
});
it("成功時はユーザーオブジェクトを返す", async () => {
const user = { id: 1, name: "Alice", email: "alice@example.com" };
fetchSpy.mockResolvedValue(mockResponse(user));
const result = await fetchUser(1);
expect(fetchSpy).toHaveBeenCalledWith("/api/users/1");
expect(result).toEqual(user);
});
it("HTTP 404 のとき Error をスローする", async () => {
fetchSpy.mockResolvedValue(mockResponse({ error: "Not Found" }, 404));
await expect(fetchUser(999)).rejects.toThrow("HTTP error: 404");
});
it("ネットワーク障害のとき Error をスローする", async () => {
fetchSpy.mockRejectedValue(new TypeError("Failed to fetch"));
await expect(fetchUser(1)).rejects.toThrow("Failed to fetch");
});
});
describe("fetchUsers", () => {
let fetchSpy: jest.SpyInstance;
beforeEach(() => {
fetchSpy = jest.spyOn(global, "fetch");
});
afterEach(() => {
fetchSpy.mockRestore();
});
it("成功時はユーザー配列を返す", async () => {
const users = [
{ id: 1, name: "Alice", email: "alice@example.com" },
{ id: 2, name: "Bob", email: "bob@example.com" },
];
fetchSpy.mockResolvedValue(mockResponse({ users }));
const result = await fetchUsers();
expect(result).toHaveLength(2);
expect(result[0].name).toBe("Alice");
});
it("HTTP 500 のとき Error をスローする", async () => {
fetchSpy.mockResolvedValue(mockResponse({ error: "Internal Server Error" }, 500));
await expect(fetchUsers()).rejects.toThrow("HTTP error: 500");
});
});
Jest 設定
// jest.config.ts
import type { Config } from "jest";
const config: Config = {
preset: "ts-jest",
testEnvironment: "node",
moduleNameMapper: {
"^@/(.*)$": "<rootDir>/src/$1",
},
};
export default config;
ポイント
jest.spyOn(global, "fetch")はfetchをモックしつつ元の実装への参照を保持する。afterEachでmockRestore()を呼ぶことで他テストへの影響を防ぐmockResolvedValueはfetchが正常に Promise を解決するケース(HTTP エラーを含む)に使う。mockRejectedValueはネットワーク切断などfetch自体が例外をスローするケースに使うResponseのokプロパティはstatus >= 200 && status < 300のときtrueになる。テスト用ヘルパーでこの挙動を再現することで、実際の Response オブジェクト生成を省略できる- テスト対象関数が
fetchの URL 引数を正しく組み立てているかtoHaveBeenCalledWithで検証する。パス構築のバグを早期に発見できる global.fetchへの代入(global.fetch = jest.fn())でも動作するが、spyOnの方が型安全でmockRestoreによる自動クリーンアップができるため推奨する