🎯 msw-auto-mock
OpenAPI 스펙을 기반으로 MSW(Mock Service Worker) 핸들러를 자동으로 생성해주는 라이브러리입니다.
✨ 주요 기능
- 🚀 OpenAPI 스펙 기반 자동 핸들러 생성
- 📁 엔티티별 핸들러 파일 분리
- 🎮 프로그래밍 방식 API 지원
- 🛠 커스텀 컨트롤러를 통한 응답 커스터마이징 (controllers 타입 추론 지원)
- 🔄 다양한 응답 타입 지원 (JSON, Event Stream 등)
📦 설치 방법
npm registry에 배포 전이므로, portal 방식으로 link합니다.
1️⃣ 프로젝트 구조 설정
먼저 my-project와 msw-auto-mock을 같은 최상위 디렉토리에 위치시킵니다:
/workspace
├── my-project/ # 구현 프로젝트
└── msw-auto-mock/ # 라이브러리
2️⃣ msw-auto-mock 설정
msw-auto-mock 디렉토리에서:
cd msw-auto-mock
pnpm install
pnpm build
3️⃣ my-project 설정
my-project 디렉토리에서:
{
"scripts": {
"generate-msw-mock": "tsx src/app/mocks/mock-generator.ts"
},
"devDependencies": {
"@dhlab/msw-auto-mock": "^0.31.0"
},
"resolutions": {
"@dhlab/msw-auto-mock": "portal:../msw-auto-mock"
}
}
cd my-project
yarn
4️⃣ my-project에 mock-generator 스크립트 추가
my-project/src/app/mocks/mock-generator.ts 파일을 생성:
import { type TOptions, generateMocks } from '@dhlab/msw-auto-mock';
import type { TControllers } from './__types__/index';
import { controllers } from './controllers/index';
async function autoGenerateMocks() {
try {
console.log('[MSW] 목 파일 생성 시작...');
const options: TOptions<TControllers> = {
controllers,
input: './swagger/openapi.yml',
outputDir: './src/app/mocks',
environment: 'react',
baseUrl: 'https://api.example.com/api/v1',
};
const result = await generateMocks(options);
console.log('[MSW] 목 파일 생성 완료!');
console.log('[MSW] 생성된 파일 경로:', result.targetFolder);
return result;
} catch (error) {
console.error('[MSW] 목 파일 생성 중 오류 발생:', error);
throw error;
}
}
autoGenerateMocks();
5️⃣ MSW 핸들러 생성
my-project 디렉토리에서:
yarn generate-msw-mock
🔧 사용 방법
기본 설정
const options: ProgrammaticOptions = {
input: './swagger/openapi.yml',
outputDir: './src/app/mocks',
baseUrl: 'https://api.example.com',
environment: 'react',
maxArrayLength: 3,
includes: '/api/v1/*',
excludes: '/api/v1/health',
codes: '200,201',
static: false,
controllers: {
getGetUsersUsersGet200Response: () => userList,
...
},
controllerPath: '@/app/mocks/controllers',
dtoImportPath: '@/shared/api/dto',
};
📁 생성되는 파일 구조
src/app/mocks/
├── __types__/
│ ├── users.type.ts
│ ├── chats.type.ts
│ ├── ...
│ ├── index.ts
├── __handlers__/
│ ├── users.handlers.ts
│ ├── chats.handlers.ts
│ └── ...
│ └── index.ts
└── browser.ts
🔄 지원하는 환경
- 🌐 React (Browser)
- ⚡ Next.js (Node.js + Browser)
- 📱 React Native
🚀 ESM 지원 및 기술적 해결 방안
Node.js 전용 기능이 CommonJS인 이유
generateMocks 함수는 다음과 같은 이유로 CommonJS로만 빌드됩니다:
- 의존성 제약:
@apidevtools/swagger-parser, swagger2openapi 등의 핵심 의존성이 ESM을 지원하지 않음
- 파일 시스템 접근: Node.js의
fs, path 모듈을 직접 사용하여 파일 생성 작업 수행
- 안정성: CommonJS 환경에서 검증된 라이브러리들과의 호환성 보장
React 프로젝트에서 ESM 사용 가능
React 프로젝트에서는 다음과 같은 방식으로 ESM을 완전히 지원합니다:
import { selectResponseByScenario, transformJSONSchemaToFakerCode } from '@dhlab/msw-auto-mock';
import { generateMocks } from '@dhlab/msw-auto-mock/node';
이중 패키지 구조의 장점
@dhlab/msw-auto-mock
├── dist/
│ ├── index.js # ESM (브라우저, React 등)
│ ├── index.cjs # CommonJS (Node.js 호환)
│ └── node/
│ └── node.cjs # Node.js 전용 CommonJS
이 구조를 통해:
- 브라우저 환경: 가벼운 ESM 번들 사용
- Node.js 환경: 안정적인 CommonJS 사용
- React 프로젝트: ESM으로 런타임 기능 사용, 빌드 스크립트는 CommonJS로 파일 생성
설치
npm install @dhlab/msw-auto-mock
yarn add @dhlab/msw-auto-mock
pnpm add @dhlab/msw-auto-mock
환경별 사용법
기본 사용법 (환경 상관없이)
환경에 상관없이 사용할 수 있는 기능들입니다.
import {
selectResponseByScenario,
transformJSONSchemaToFakerCode,
type TOptions,
type TScenarioConfig,
type ResponseObject
} from '@dhlab/msw-auto-mock';
Node.js 환경 (코드 생성)
Node.js 환경에서는 OpenAPI 스키마를 기반으로 MSW 핸들러 파일을 생성할 수 있습니다.
import { generateMocks } from '@dhlab/msw-auto-mock/node';
import {
selectResponseByScenario,
transformJSONSchemaToFakerCode,
type TOptions
} from '@dhlab/msw-auto-mock';
await generateMocks({
input: 'path/to/openapi.json',
outputDir: 'src/mocks',
environment: 'react'
});
React 컴포넌트에서 사용 예제
import React, { useState, useEffect } from 'react';
import { http, HttpResponse } from 'msw';
import { selectResponseByScenario, transformJSONSchemaToFakerCode } from '@dhlab/msw-auto-mock';
const MyComponent: React.FC = () => {
const [mockData, setMockData] = useState(null);
useEffect(() => {
const scenarios = {
'user-error': {
description: '사용자 오류 시나리오',
api: {
'/api/users': {
'GET': { status: 400 }
}
}
},
'success': {
description: '성공 시나리오',
api: {
'/api/users': {
'GET': { status: 200 }
}
}
},
'custom-error': {
description: '커스텀 에러 시나리오',
api: {
'/api/users': {
'GET': { status: 418, allowCustomStatus: true }
}
}
}
};
const handler = http.get('/api/users', (info) => {
const responses = [
{ status: 200, responseType: 'application/json', body: JSON.stringify({ users: [] }) },
{ status: 400, responseType: 'application/json', body: JSON.stringify({ error: 'Bad Request' }) },
{ status: 500, responseType: 'application/json', body: JSON.stringify({ error: 'Server Error' }) }
];
const selectedResponse = selectResponseByScenario('GET', '/api/users', responses, info, scenarios);
return HttpResponse.json(
JSON.parse(selectedResponse.body || '{}'),
{ status: selectedResponse.status }
);
});
const userSchema = {
type: 'object',
properties: {
id: { type: 'string', format: 'uuid' },
name: { type: 'string' },
email: { type: 'string', format: 'email' },
age: { type: 'integer', minimum: 18, maximum: 100 }
}
};
const fakerCode = transformJSONSchemaToFakerCode(userSchema);
console.log('Generated faker code:', fakerCode);
}, []);
return (
<div>
<h1>MSW Auto Mock React Example</h1>
{/* 컴포넌트 내용 */}
</div>
);
};
Next.js에서 사용 예제
import { generateMocks } from '@dhlab/msw-auto-mock/node';
import type { TOptions } from '@dhlab/msw-auto-mock';
export default async function handler(req: any, res: any) {
if (process.env.NODE_ENV === 'development') {
const options: TOptions = {
input: './public/openapi.json',
outputDir: './src/mocks',
environment: 'next'
};
await generateMocks(options);
res.status(200).json({ message: 'Mocks generated successfully' });
} else {
res.status(404).json({ message: 'Not found' });
}
}
import React from 'react';
import { selectResponseByScenario } from '@dhlab/msw-auto-mock';
export const MockProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
React.useEffect(() => {
if (typeof window !== 'undefined') {
import('../mocks/browser').then(({ worker }) => {
worker.start();
});
}
}, []);
return <>{children}</>;
};
API 문서
환경 상관없이 사용 가능한 API
selectResponseByScenario
시나리오 기반으로 응답을 선택하는 함수입니다. 헤더 x-scenario를 통해 특정 시나리오를 활성화할 수 있습니다.
function selectResponseByScenario(
verb: string,
path: string,
resultArray: ResponseObject[],
info: Parameters<HttpResponseResolver<Record<string, never>, null>>[0],
scenarios?: TScenarioConfig
): ResponseObject
allowCustomStatus 기능
기본적으로 시나리오에서는 OpenAPI 명세에 정의된 status 코드만 사용할 수 있습니다. 하지만 allowCustomStatus: true를 설정하면 명세에 없는 임의의 status 코드도 사용할 수 있습니다.
- 기본 동작: OpenAPI 명세에 정의된 status 코드만 허용 (타입 안전성 보장)
- allowCustomStatus: true: 임의의 status 코드 사용 가능 (동적 에러 응답 생성)
사용 예시:
const scenarios = {
'success': {
description: '성공 시나리오',
api: {
'/api/users': { 'GET': { status: 200 } }
}
},
'error': {
description: '에러 시나리오',
api: {
'/api/users': { 'GET': { status: 500 } }
}
},
'custom-error': {
description: '커스텀 에러 시나리오',
api: {
'/api/users': {
'GET': {
status: 418,
allowCustomStatus: true
}
}
}
}
};
const handler = http.get('/api/users', (info) => {
const responses = [
{ status: 200, responseType: 'application/json', body: '{"users": []}' },
{ status: 500, responseType: 'application/json', body: '{"error": "Server Error"}' }
];
const selectedResponse = selectResponseByScenario('GET', '/api/users', responses, info, scenarios);
return HttpResponse.json(JSON.parse(selectedResponse.body || '{}'), {
status: selectedResponse.status
});
});
테스트 시나리오 제어:
curl -H "x-scenario: success" http://localhost:3000/api/users
curl -H "x-scenario: error" http://localhost:3000/api/users
curl -H "x-scenario: custom-error" http://localhost:3000/api/users
curl http://localhost:3000/api/users
transformJSONSchemaToFakerCode
OpenAPI 스키마를 Faker.js 코드로 변환하는 함수입니다.
function transformJSONSchemaToFakerCode(
jsonSchema?: OpenAPIV3.SchemaObject,
key?: string
): string
🎯 allowCustomStatus 기능 상세 가이드
사용 시나리오
allowCustomStatus: true 기능은 다음과 같은 상황에서 유용합니다:
- 테스트 전용 에러 코드: 특정 테스트 시나리오에서만 사용하는 에러 코드
- OpenAPI 명세 미정의 응답: 명세에는 없지만 실제 서버에서 발생할 수 있는 응답
- 프로토타이핑: 새로운 API 응답을 실험하기 위한 임시 응답
동작 방식
const scenarios = {
'teapot-error': {
description: '티팟 에러 테스트',
api: {
'/api/users': {
'GET': {
status: 418,
allowCustomStatus: true
}
}
}
}
};
생성되는 응답
allowCustomStatus: true로 설정된 시나리오가 활성화되면:
{
"error": "Client Error",
"status": 418
}
또는
{
"error": "Internal Server Error",
"status": 503
}
타입 안전성
TypeScript를 사용하는 경우:
const badScenario = {
api: {
'/api/users': {
'GET': { status: 418 }
}
}
};
const goodScenario = {
api: {
'/api/users': {
'GET': { status: 418, allowCustomStatus: true }
}
}
};
주의사항
- 명세 일치성:
allowCustomStatus는 명세와 목업 간의 일치성을 일부 포기하는 트레이드오프입니다
- 테스트 전용: 프로덕션 환경이 아닌 테스트/개발 환경에서만 사용하는 것을 권장합니다
- 문서화: 커스텀 status를 사용하는 경우 팀 내에서 충분한 문서화가 필요합니다
타입 정의
export type TScenarioConfig = {
[scenarioId: string]: {
description: string;
api: Record<string, Record<string, {
status: number;
allowCustomStatus?: boolean;
}>>;
};
};
export type ResponseObject = {
status: number;
responseType: string | undefined;
body: string | undefined;
};
패키지 구조
이 라이브러리는 기능별로 분리된 패키지로 제공됩니다:
- 기본 (메인 엔트리): 환경 상관없이 사용 가능한 기능들
- Node.js 전용: 파일 시스템 접근이 필요한 코드 생성 기능
@dhlab/msw-auto-mock
├── dist/
│ ├── index.js # 기본 ESM (환경 상관없이)
│ ├── index.cjs # 기본 CommonJS (환경 상관없이)
│ └── node/
│ ├── node.js # Node.js 전용 ESM
│ └── node.cjs # Node.js 전용 CommonJS
사용법 요약
- 환경 상관없이 사용 가능:
import { ... } from '@dhlab/msw-auto-mock'
- Node.js 전용 기능:
import { generateMocks } from '@dhlab/msw-auto-mock/node'
실제 사용 예시
import { selectResponseByScenario, transformJSONSchemaToFakerCode } from '@dhlab/msw-auto-mock';
import { generateMocks } from '@dhlab/msw-auto-mock/node';
import { selectResponseByScenario } from '@dhlab/msw-auto-mock';
import { generateMocks } from '@dhlab/msw-auto-mock/node';
라이선스
MIT