MSW(モックサービスワーカー)
MSW層: APIのモック(開発・テスト環境)
Section titled “MSW層: APIのモック(開発・テスト環境)”役割: 開発・テスト環境でAPIサーバーをモックし、実際のAPIサーバーに依存せずに開発・テストを進める。
重要なポイント: MSWはService Workerを使用してネットワークリクエストをインターセプトし、モックレスポンスを返す。
セットアップ
Section titled “セットアップ”npm install --save-dev mswimport { http, HttpResponse } from 'msw';
// モックハンドラー定義export const handlers = [ // GET /api/users/:id http.get('https://api.example.com/users/:id', ({ params }) => { const { id } = params;
return HttpResponse.json({ id: Number(id), first_name: '太郎', last_name: '山田', email: 'yamada@example.com', created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z', }); }),
// GET /api/users http.get('https://api.example.com/users', () => { return HttpResponse.json([ { id: 1, first_name: '太郎', last_name: '山田', email: 'yamada@example.com', created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z', }, { id: 2, first_name: '花子', last_name: '佐藤', email: 'sato@example.com', created_at: '2024-01-02T00:00:00Z', updated_at: '2024-01-02T00:00:00Z', }, ]); }),
// POST /api/users http.post('https://api.example.com/users', async ({ request }) => { const body = await request.json();
return HttpResponse.json({ id: Math.floor(Math.random() * 1000), ...body, created_at: new Date().toISOString(), updated_at: new Date().toISOString(), }, { status: 201 }); }),
// PUT /api/users/:id http.put('https://api.example.com/users/:id', async ({ params, request }) => { const { id } = params; const body = await request.json();
return HttpResponse.json({ id: Number(id), ...body, updated_at: new Date().toISOString(), }); }),];ブラウザ環境でのセットアップ
Section titled “ブラウザ環境でのセットアップ”import { setupWorker } from 'msw/browser';import { handlers } from './handlers';
// Service Workerをセットアップexport const worker = setupWorker(...handlers);Next.jsでの使用例
Section titled “Next.jsでの使用例”// app/layout.tsx (開発環境のみ)import { worker } from '@/mocks/browser';
if (process.env.NODE_ENV === 'development') { // MSWを起動(ブラウザ環境) worker.start({ onUnhandledRequest: 'bypass', // ハンドラーが定義されていないリクエストはそのまま通過 });}
export default function RootLayout({ children,}: { children: React.ReactNode;}) { return ( <html lang="ja"> <body>{children}</body> </html> );}Reactでの使用例
Section titled “Reactでの使用例”// src/index.tsx (開発環境のみ)import { worker } from './mocks/browser';
if (process.env.NODE_ENV === 'development') { // MSWを起動(ブラウザ環境) worker.start({ onUnhandledRequest: 'bypass', });}
// Reactアプリの起動ReactDOM.createRoot(document.getElementById('root')!).render( <React.StrictMode> <App /> </React.StrictMode>);Node.js環境でのセットアップ(テスト環境)
Section titled “Node.js環境でのセットアップ(テスト環境)”import { setupServer } from 'msw/node';import { handlers } from './handlers';
// テストサーバーをセットアップexport const server = setupServer(...handlers);// src/setupTests.ts (Jest/Vitest設定)import { server } from './mocks/server';
// テスト開始前にMSWサーバーを起動beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
// 各テスト後にハンドラーをリセットafterEach(() => server.resetHandlers());
// テスト終了後にMSWサーバーを停止afterAll(() => server.close());MSWとSchema層の連携
Section titled “MSWとSchema層の連携”MSWのモックレスポンスは、Schema層で定義したZodスキーマに準拠させることで、型安全性を保つことができます。
import { http, HttpResponse } from 'msw';import { userApiSchema, usersApiSchema } from '@/api/schema/userSchema';import { z } from 'zod';
// Schema層のスキーマを使用してモックデータを型安全に定義const mockUser = userApiSchema.parse({ id: 1, first_name: '太郎', last_name: '山田', email: 'yamada@example.com', created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z',});
export const handlers = [ http.get('https://api.example.com/users/:id', ({ params }) => { const { id } = params;
// Schema層のスキーマで型チェック const user = userApiSchema.parse({ ...mockUser, id: Number(id), });
return HttpResponse.json(user); }),
http.get('https://api.example.com/users', () => { // Schema層のスキーマで型チェック const users = usersApiSchema.parse([ mockUser, { ...mockUser, id: 2, first_name: '花子', last_name: '佐藤', email: 'sato@example.com', }, ]);
return HttpResponse.json(users); }),];エラーハンドリングのモック
Section titled “エラーハンドリングのモック”エラーケースもモックすることで、エラーハンドリングのテストが可能です。
import { http, HttpResponse } from 'msw';
export const handlers = [ // 正常系 http.get('https://api.example.com/users/:id', ({ params }) => { const { id } = params;
return HttpResponse.json({ id: Number(id), first_name: '太郎', last_name: '山田', email: 'yamada@example.com', created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z', }); }),
// エラーケース: 404 Not Found http.get('https://api.example.com/users/999', () => { return HttpResponse.json( { error: 'User not found' }, { status: 404 } ); }),
// エラーケース: 500 Internal Server Error http.get('https://api.example.com/users/error', () => { return HttpResponse.json( { error: 'Internal server error' }, { status: 500 } ); }),];動的なモックデータの生成
Section titled “動的なモックデータの生成”テストで異なるデータが必要な場合、動的にモックデータを生成できます。
import { http, HttpResponse } from 'msw';import { userApiSchema } from '@/api/schema/userSchema';
// モックデータ生成関数function generateMockUser(id: number, firstName: string, lastName: string, email: string) { return userApiSchema.parse({ id, first_name: firstName, last_name: lastName, email, created_at: new Date().toISOString(), updated_at: new Date().toISOString(), });}
export const handlers = [ http.get('https://api.example.com/users/:id', ({ params }) => { const { id } = params; const userId = Number(id);
// 動的にモックデータを生成 const user = generateMockUser( userId, `ユーザー${userId}`, 'テスト', `user${userId}@example.com` );
return HttpResponse.json(user); }),];ベストプラクティス
Section titled “ベストプラクティス”- Schema層との統合: MSWのモックレスポンスはSchema層のZodスキーマを使用
- 環境別の有効化: 開発・テスト環境でのみMSWを有効化
- リアルなモック: 実際のAPIレスポンスと同じ形式でモックデータを定義
- エラーハンドリング: エラーケースもモック(4xx、5xxなど)
- 動的なデータ生成: テストで異なるデータが必要な場合、動的に生成する関数を作成
パイプライン設計との連携
Section titled “パイプライン設計との連携”MSWはパイプライン設計の一部として、以下のようにデータフローに組み込まれます:
MSW層: 開発・テスト環境でAPIをモック(実際のAPIサーバーをバイパス) ↓API層: unknown -> Strict Schema (Zodで洗浄) ↓DTO層: API型 -> UI型 (snake_case -> camelCase, 計算プロパティ追加) ↓...MSW層でモックしたデータは、Schema層で定義したZodスキーマに準拠させることで、型安全性を保ちながら開発・テストを進めることができます。