第211章:モックの考え方(やりすぎ注意)🎭🧪
今日のゴール 🎯✨
- **モック(mock)って何のため?**をスッキリ理解する😊
- **「どこをモックして、どこは本物のまま?」**の判断ができるようになる🧠
- Vitestでよく使う
vi.fn/vi.spyOn/vi.mock/vi.stubGlobalを“必要な分だけ”使えるようになる🪄 (vitest.dev)
1) モックってなに?🎭(超ざっくり)
モックは、テスト中だけ登場する 「代役(スタント)」 だよ〜!🎬✨ 本物に頼ると、テストがこうなりがち👇
- ネットワークが遅い/落ちる→テストが不安定😵💫
- 時刻や乱数で結果が変わる→再現できない😭
- ルーターや外部SDKが重い→テストが遅い🐢
だから “外部のもの”だけ代役にして、テストを安定させるのが目的だよ💪🎭
2) 似てる子たち:Mock / Stub / Spy 👯♀️✨
ざっくり使い分けるとこんな感じ!
- Stub(スタブ):戻り値だけ決める係📦(「これ返してね」)
- Mock(モック):呼ばれ方もチェックしたい係📞(「何回呼んだ?引数は?」)
- Spy(スパイ):本物を見張る係🕵️(「本物は動かしつつ、呼び出しログだけ取る」)
Vitestだと、だいたいこの3つでOK👇
vi.fn():モック関数を作る🎭 (vitest.dev)vi.spyOn(obj, "method"):スパイする🕵️ (vitest.dev)
3) どこをモックする?「境界だけ」がおすすめ🚧✨
コツはシンプルで、アプリの“境界”だけモックするのが安定しやすいよ🎯 (境界=ネットワーク、時間、ブラウザAPI、Nextのルーター、外部SDKなど)
図でいうとここ👇
4) やりすぎ注意ポイント⚠️🧊(モックしすぎると起きる事故)
モックを増やしすぎると、テストがこうなりがち👇
- 実装の細部に依存して、ちょっとリファクタでテスト崩壊💥
- 「画面として正しいか」より「呼び出しの順番が同じか」みたいなテストになる🌀
- 本番では壊れてるのにテストだけ通る…😇
Testing Library系の考え方は、ざっくり “ユーザー視点でテストしよう” なので、内部実装を当てにしすぎるモックは増やしすぎ注意だよ〜!🫶 (Kent C. Dodds)
5) 判断フローチャート🧭✨(迷ったらこれ)
6) Vitestでの定番モック3点セット🧰✨
6-1) vi.fn():モック関数(呼ばれたか確認したい)📞🎭
import { vi, expect, test } from "vitest";
test("モック関数が呼ばれる", () => {
const onClick = vi.fn();
onClick("hello");
expect(onClick).toHaveBeenCalledTimes(1);
expect(onClick).toHaveBeenCalledWith("hello");
});
vi.fn は「呼び出し回数・引数」チェックに強いよ💪 (vitest.dev)
6-2) vi.spyOn():本物を動かしつつ記録🕵️✨
「ログが出たか」みたいなのに便利〜!
import { vi, expect, test } from "vitest";
test("console.error を監視する", () => {
const spy = vi.spyOn(console, "error").mockImplementation(() => {});
console.error("oops");
expect(spy).toHaveBeenCalledWith("oops");
spy.mockRestore(); // 後片付け🧹
});
※スパイは 後片付け 大事だよ🧹✨(忘れると他のテストに影響しがち)
6-3) vi.mock():モジュールごと差し替え(Nextのhook等)🧩🎭
Next.js(App Router)で超よくあるのが next/navigation のhooksだよね🚀
テストだと「Routerがマウントされてないよ」系のエラーが出やすくて、ここをモックするのが定番!🛣️ (GitHub)
例:ボタン押したら router.push("/mypage") するコンポーネントをテスト✨
コンポーネント(例:app/components/GoMyPageButton.tsx)
"use client";
import { useRouter } from "next/navigation";
export function GoMyPageButton() {
const router = useRouter();
return (
<button onClick={() => router.push("/mypage")}>
マイページへ
</button>
);
}
テスト(例:GoMyPageButton.test.tsx)
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { vi, expect, test } from "vitest";
import { GoMyPageButton } from "./GoMyPageButton";
const pushMock = vi.fn();
vi.mock("next/navigation", () => ({
useRouter: () => ({ push: pushMock }),
}));
test("クリックで /mypage に遷移する", async () => {
const user = userEvent.setup();
render(<GoMyPageButton />);
await user.click(screen.getByRole("button", { name: "マイページへ" }));
expect(pushMock).toHaveBeenCalledWith("/mypage");
});
ポイント🎯
- ルーター本体を再現しようとしない🙅♀️
- このテストで必要な最小限(
pushだけ) を差し替える🎭✨
7) fetchをモックする:2つの考え方🌊🧪
A) “手でfetchを差し替える”(小さく済むならOK)🧤
Vitestには vi.stubGlobal があるよ🪄(ただし自動リセットに注意!) (vitest.dev)
import { vi, afterEach } from "vitest";
afterEach(() => {
vi.unstubAllGlobals(); // 後片付け🧹(または config で unstubGlobals)
});
vi.stubGlobal("fetch", vi.fn(async () => {
return {
ok: true,
json: async () => ({ name: "Hanako" }),
} as any;
}));
B) “MSWでAPIを自然にモック”(おすすめされがち)🛰️✨
Testing Libraryの例では、fetch を直接スタブするより MSW(Mock Service Worker) みたいに「通信っぽく」モックするのが推されることがあるよ📮 (テスティングライブラリ)
(この章では“考え方”だけでOK!次の章や課題で導入でも🙆♀️)
8) Next.js(App Router)だと、ここ注意⚠️🧊
-
asyncな Server Component は、ユニットテスト(Vitest)で扱いづらいケースがあるよ〜。そういうのは E2E寄りで見る判断もアリ🎬 (Next.js) -
だからこそ、ユニットテストでは
- Client ComponentのUIと操作
- 境界(Router/fetch等)を最小モック が気持ちよくまとまりやすいよ😊✨
9) まとめ:モックは「必要な分だけ」🎭🫶
覚える合言葉はこれ〜!💞
- ✅ 外部(境界)だけモックしがち
- ✅ 戻り値固定+呼ばれ方確認が主な用途
- ⚠️ 実装の細部を再現しようとしない(テストが壊れやすくなる) (Kent C. Dodds)
ミニ練習(3分)⏱️🐣
GoMyPageButtonをコピーして、"/settings"に飛ぶボタンを作ってみてね⚙️✨- テストでは
pushMockが"/settings"で呼ばれたか確認してみよう🎯🎭 - 余裕があったら、ボタン文言を変えてもテストが壊れにくい書き方(
getByRole)を意識してみてね👀💡