Tips & Tricks

Claude CodeでMSW APIモックを設計・実装する

MSW(Mock Service Worker)を使ったAPIモックの設計をClaude Codeで効率化する方法を解説。ハンドラー設計、テスト統合、開発サーバー活用まで実践パターンを紹介します。

MSWでリアルなAPIモック環境を構築する

MSW(Mock Service Worker)はService Workerを利用してネットワークレベルでAPIリクエストをインターセプトするモックライブラリです。テストとブラウザ開発の両方で同じモックハンドラーを共有でき、実際のHTTPリクエストと同じ振る舞いを再現します。Claude Codeはハンドラーの設計から型安全なモック構築まで的確にサポートします。

基本的なハンドラー設計

Claude Codeにハンドラーの構成を依頼しましょう。

> MSWでユーザーAPIのモックハンドラーを設計して。
> CRUD操作、エラーハンドリング、レスポンス遅延を含めて。
import { http, HttpResponse, delay } from "msw";

// インメモリデータベース
let users: User[] = [
  { id: "1", name: "Alice", email: "[email protected]", role: "admin" },
  { id: "2", name: "Bob", email: "[email protected]", role: "editor" },
];

export const userHandlers = [
  // 一覧取得
  http.get("/api/users", async ({ request }) => {
    await delay(100);
    const url = new URL(request.url);
    const page = Number(url.searchParams.get("page") || "1");
    const perPage = Number(url.searchParams.get("perPage") || "20");
    const search = url.searchParams.get("search") || "";

    let filtered = users;
    if (search) {
      filtered = users.filter(
        (u) =>
          u.name.toLowerCase().includes(search.toLowerCase()) ||
          u.email.toLowerCase().includes(search.toLowerCase())
      );
    }

    const start = (page - 1) * perPage;
    const paginated = filtered.slice(start, start + perPage);

    return HttpResponse.json({
      data: paginated,
      meta: { total: filtered.length, page, perPage },
    });
  }),

  // 詳細取得
  http.get("/api/users/:id", async ({ params }) => {
    await delay(50);
    const user = users.find((u) => u.id === params.id);
    if (!user) {
      return HttpResponse.json(
        { error: "ユーザーが見つかりません" },
        { status: 404 }
      );
    }
    return HttpResponse.json({ data: user });
  }),

  // 作成
  http.post("/api/users", async ({ request }) => {
    await delay(200);
    const body = (await request.json()) as Omit<User, "id">;
    const newUser: User = { ...body, id: String(Date.now()) };
    users.push(newUser);
    return HttpResponse.json({ data: newUser }, { status: 201 });
  }),

  // 更新
  http.put("/api/users/:id", async ({ params, request }) => {
    await delay(150);
    const body = (await request.json()) as Partial<User>;
    const index = users.findIndex((u) => u.id === params.id);
    if (index === -1) {
      return HttpResponse.json(
        { error: "ユーザーが見つかりません" },
        { status: 404 }
      );
    }
    users[index] = { ...users[index], ...body };
    return HttpResponse.json({ data: users[index] });
  }),

  // 削除
  http.delete("/api/users/:id", async ({ params }) => {
    await delay(100);
    users = users.filter((u) => u.id !== params.id);
    return new HttpResponse(null, { status: 204 });
  }),
];

テストでの統合

Vitestとの統合パターンです。

import { setupServer } from "msw/node";
import { userHandlers } from "./handlers/user";

// テスト用サーバー
const server = setupServer(...userHandlers);

beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

describe("UserList コンポーネント", () => {
  it("ユーザー一覧を表示する", async () => {
    render(<UserList />);

    await waitFor(() => {
      expect(screen.getByText("Alice")).toBeInTheDocument();
      expect(screen.getByText("Bob")).toBeInTheDocument();
    });
  });

  it("APIエラー時にエラーメッセージを表示する", async () => {
    // テスト単位でハンドラーを上書き
    server.use(
      http.get("/api/users", () => {
        return HttpResponse.json(
          { error: "サーバーエラー" },
          { status: 500 }
        );
      })
    );

    render(<UserList />);

    await waitFor(() => {
      expect(screen.getByText("データの取得に失敗しました")).toBeInTheDocument();
    });
  });

  it("ネットワークエラー時にリトライボタンが表示される", async () => {
    server.use(
      http.get("/api/users", () => {
        return HttpResponse.error();
      })
    );

    render(<UserList />);

    await waitFor(() => {
      expect(screen.getByRole("button", { name: "再試行" })).toBeInTheDocument();
    });
  });
});

ブラウザでの開発用モック

ブラウザ環境でService Workerとして動作させるパターンです。

// src/mocks/browser.ts
import { setupWorker } from "msw/browser";
import { userHandlers } from "./handlers/user";
import { authHandlers } from "./handlers/auth";

export const worker = setupWorker(...userHandlers, ...authHandlers);

// src/main.tsx
async function enableMocking() {
  if (import.meta.env.DEV) {
    const { worker } = await import("./mocks/browser");
    return worker.start({
      onUnhandledRequest: "bypass",
    });
  }
}

enableMocking().then(() => {
  ReactDOM.createRoot(document.getElementById("root")!).render(
    <React.StrictMode>
      <App />
    </React.StrictMode>
  );
});

型安全なハンドラー設計

TypeScriptの型とMSWハンドラーを統合するパターンです。

import { http, HttpResponse } from "msw";

// API型定義
interface ApiEndpoints {
  "GET /api/users": {
    response: { data: User[]; meta: PaginationMeta };
    params: never;
  };
  "GET /api/users/:id": {
    response: { data: User };
    params: { id: string };
  };
  "POST /api/users": {
    response: { data: User };
    body: CreateUserInput;
    params: never;
  };
}

// ファクトリー関数
function createUserFactory(overrides: Partial<User> = {}): User {
  return {
    id: String(Math.random()).slice(2, 10),
    name: `User ${Math.random().toString(36).slice(2, 6)}`,
    email: `user-${Date.now()}@example.com`,
    role: "viewer",
    ...overrides,
  };
}

// テストシナリオ用のハンドラーセット
export const emptyStateHandlers = [
  http.get("/api/users", () =>
    HttpResponse.json({ data: [], meta: { total: 0, page: 1, perPage: 20 } })
  ),
];

export const errorHandlers = [
  http.get("/api/users", () =>
    HttpResponse.json({ error: "Internal Server Error" }, { status: 500 })
  ),
];

export const slowResponseHandlers = [
  http.get("/api/users", async () => {
    await delay(5000);
    return HttpResponse.json({ data: [createUserFactory()], meta: { total: 1, page: 1, perPage: 20 } });
  }),
];

まとめ

MSWはネットワークレベルのモックにより、実際のAPI通信に近い環境でテストと開発ができます。Claude Codeを活用すれば、ハンドラー設計、テスト統合、型安全な構成を短時間で構築可能です。

ユニットテストとの連携はVitest上級テクニックを、E2Eテストでの活用はPlaywright E2Eテスト実践ガイドを参照してください。MSW公式ドキュメントも確認しておきましょう。

#Claude Code #MSW #APIモック #テスト #フロントエンド