백엔드 API가 완전히 준비되기 전에 프론트엔드 개발을 시작해야 할 때, Mock API는 효율적인 대안이 될 수 있습니다. **MSW(Mock Service Worker)**는 API 요청을 가로채서 사전에 정의된 Mock 데이터를 반환하도록 도와주는 라이브러리입니다. 이 글에서는 MSW를 활용해 Mock API를 구현하는 과정을 단계별로 설명합니다.
.
백엔드 API가 완전히 준비되기 전에 프론트엔드 개발을 시작해야 할 때, Mock API는 효율적인 대안이 될 수 있습니다. **MSW(Mock Service Worker)**는 API 요청을 가로채서 사전에 정의된 Mock 데이터를 반환하도록 도와주는 라이브러리입니다. 이 글에서는 MSW를 활용해 Mock API를 구현하는 과정을 단계별로 설명합니다.
1. MSW란 무엇인가?
MSW(Mock Service Worker)는 브라우저와 Node.js 환경에서 동작하며, 클라이언트의 네트워크 요청을 가로채서 Mock 데이터를 반환하는 역할을 합니다. MSW의 주요 특징은 다음과 같습니다:
- 프론트엔드 중심 개발: 백엔드 API 없이도 프론트엔드 작업을 시작할 수 있음.
- 유연한 요청 처리: 요청별 핸들러를 정의해 동작을 세부적으로 설정 가능.
- 실제 API 호출과 동일한 환경 시뮬레이션: 브라우저의 네트워크 요청 흐름을 완벽히 모방.
2. 디렉토리 구조
아래와 같은 디렉토리 구조를 활용하여 MSW의 핸들러와 관련 로직을 정리합니다:
src
├── mocks
│ ├── browser.ts // MSW 브라우저 워커 설정
│ ├── handlers // API 핸들러
│ │ ├── staticHandler.ts // 간단한 Mock API 핸들러
│ │ ├── actionHandler.ts // 동작 기반 API 핸들러
│ │ └── common.ts // 공통 로직
│ └── handlers.ts // 핸들러 묶음
├── json
│ ├── register.json // Mock 데이터
│ ├── user.json // Mock 데이터
3. MSW 설정 코드
3-1. browser.ts: 브라우저 워커 설정
import { handlers } from "./handlers";
export const worker = setupWorker(...handlers);
import { setupWorker } from "msw/browser";
import { handlers } from "./handlers";
export const worker = setupWorker(...handlers);
- setupWorker는 브라우저 환경에서 Mock Service Worker를 설정합니다.
- handlers는 요청을 처리하는 핸들러 배열입니다.
4. 핸들러 구현
4-1. 핸들러 묶음
handlers.ts 파일에서 모든 핸들러를 결합합니다.
import { staticHandlers } from "./handlers/staticHandler";
import { actionHandlers } from "./handlers/actionHandler";
export const handlers = [...staticHandlers, ...actionHandlers];
4-2. 공통 로직
handlers/common.ts 파일에서는 공통 응답 구조와 유틸리티를 정의합니다.
import { HttpResponse } from "msw";
export const path = import.meta.env.VITE_API_URL;
export const jsonResponse = (data: any) => HttpResponse.json(data);
const createResponse = (
results?: any,
status?: {
code: string;
error: boolean;
message: string;
popup_type: string;
}
) => jsonResponse({ results, status });
export const successResponse = (results: any, message: string) =>
createResponse(results, {
code: "0000",
error: false,
message: message,
popup_type: "",
});
export const failResponse = (results: any, code: string, message: string) =>
createResponse(results, {
code: code,
error: true,
message: message,
popup_type: "alert",
});
- successResponse와 failResponse를 통해 성공 및 실패 응답 구조를 쉽게 생성합니다.
4-3. 동작 기반 핸들러
handlers/actionHandler.ts 파일에서는 API 요청을 세부적으로 처리하는 핸들러를 정의합니다.
import { http } from "msw";
import { failResponse, jsonResponse, path, successResponse } from "./common";
async function parseRequestBody<T>(request: Request): Promise<T> {
return request.json();
}
export const actionHandlers = [
http.post(`${path}/api/auth/token`, async ({ request }) => {
const { identity, password } = await parseRequestBody(request);
const isValidUser = identity === "tester" && password === "1234qwer";
return isValidUser
? successResponse({ success: true }, "사용 가능합니다.")
: failResponse({}, "9800", "입력하신 로그인 정보가 잘못되었습니다.");
}),
http.post(`${path}/api/auth/duplicated`, async ({ request }) => {
const { identity } = await parseRequestBody(request);
const isDuplicated = identity === "tester";
return isDuplicated
? failResponse({}, "9100", "identity 값은 이미 사용 중입니다.")
: successResponse({ success: true }, "사용 가능합니다.");
}),
http.post(`${path}/api/auth/register`, async ({ request }) => {
const { email } = await parseRequestBody(request);
const isRegistered = email === "tester@mt.co.kr";
return isRegistered
? failResponse({}, "9842", "이미 가입된 이메일입니다.")
: jsonResponse(require("../json/register.json"));
}),
];
4-4. 간단한 정적 핸들러
handlers/staticHandler.ts 파일에서는 간단한 Mock 데이터를 반환하는 핸들러를 정의합니다.
import { http } from "msw";
import { jsonResponse, path } from "./common";
import test1 from "../json/user.json";
import test2 from "../json/user.json";
const routes = [
{ method: "get", url: `${path}/api/user/`, json: test1 },
{ method: "post", url: `${path}/api/auth/user`, json: test2 },
];
export const staticHandlers = routes.map(({ method, url, json }) => {
return http[method.toLowerCase() as keyof typeof http](url, () => {
return jsonResponse(json);
});
});
5. MSW 활용하기
MSW 설정은 보통 프로젝트 초기화 시 실행합니다. React 프로젝트의 예로 들면:
src/index.tsx
import { worker } from "./mocks/browser";
if (process.env.NODE_ENV === "development") {
worker.start();
}
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
6. MSW를 사용한 개발의 장점
- 빠른 개발 시작: 백엔드 준비 없이 API 호출과 관련된 프론트엔드 작업을 진행할 수 있음.
- 일관된 테스트 환경: Mock 데이터를 활용하여 다양한 시나리오를 검증 가능.
- 유연한 데이터 처리: 조건별 데이터 처리 및 다양한 응답 상태 시뮬레이션.
- 유지보수성: 핸들러와 Mock 데이터를 명확히 구분해 관리 용이.
7. 결론
MSW를 활용하면 백엔드와 독립적으로 프론트엔드 개발을 진행할 수 있어 생산성을 크게 높일 수 있습니다. 또한, 테스트 환경에서 API 요청과 응답을 자유롭게 시뮬레이션할 수 있어 보다 안정적이고 효율적인 개발 워크플로우를 구축할 수 있습니다. Mock API를 활용한 개발이 필요한 프로젝트에서 MSW는 매우 강력한 도구가 될 것입니다.