Mock Service Worker(MSW)를 이용한 API 모킹하기
iskkiri • 2024년 11월 03일
현대 웹 개발에서 클라이언트와 서버 간의 데이터 통신은 필수적입니다. 그러나 프론트엔드 개발 중에는 백엔드 API가 준비되지 않았거나, 네트워크와의 의존성을 제거하고 테스트를 진행해야 하는 경우가 많습니다. 이러한 상황에서 Mock Service Worker (MSW)는 API 모킹을 간단하게 구현할 수 있도록 도와주는 강력한 도구로, 실제 서버 없이도 클라이언트 요청을 시뮬레이션하여 다양한 상황에서 유용하게 활용될 수 있습니다.
이 글에서는 MSW의 개념과 장점, 그리고 함께 사용할 수 있는 @mswjs/data 라이브러리를 소개하고, 간단한 예제를 통해 두 라이브러리를 사용하여 API 모킹을 효과적으로 활용할 수 있는 방법을 알아보겠습니다.
전체 예제 코드 및 실행 동작은 Codesandbox에서 확인할 수 있습니다
MSW
Mock Service Worker (MSW)는 HTTP 요청을 가로채고, 이를 모킹하여 원하는 응답을 반환할 수 있는 라이브러리입니다. 브라우저와 Node.js 환경 모두에서 작동하며, 클라이언트 측에서 서비스 워커를 사용하여 네트워크 요청을 가로채기 때문에 실제 서버 없이도 네트워크 요청을 시뮬레이션할 수 있습니다.
MSW의 장점
네트워크 독립성
네트워크 상태와 관계없이 일관된 응답을 받을 수 있어, 개발과 테스트 환경에서 안정적으로 사용할 수 있습니다.
개발 효율성
백엔드 API가 준비되지 않았거나 기능이 불완전한 경우에도 MSW를 통해 프론트엔드 개발을 독립적으로 진행할 수 있습니다. 협업 속도를 높이고, UI와 로직을 실제 서버와의 연동 없이 테스트할 수 있습니다.
유연한 테스트 환경 구성
다양한 상황(예: 오류 응답, 지연 시간)을 쉽게 시뮬레이션하여 예외 상황에 대한 테스트를 단순화합니다.
사용자 경험에 근접한 시뮬레이션
실제 네트워크 요청을 모방하여 사용자가 실제 서버와 상호작용하는 환경과 유사하게 테스트할 수 있습니다.
MSW 설치 및 설정하기
MSW 설치
MSW는 브라우저와 Node.js에서 사용할 수 있으며, 환경에 따라 설정이 다릅니다. 이 예제에서는 브라우저 환경을 기준으로 설정을 진행합니다.
npm install msw --save-dev
Worker Script 생성
MSW는 서비스 워커를 통해 요청을 가로채므로, 워커 파일을 생성해야 합니다. 다음 명령어를 통해 기본 mockServiceWorker.js 파일을 public 폴더에 생성합니다.
npx msw init public --save
애플리케이션에 MSW 설정
애플리케이션의 진입점인 main.tsx(index.tsx) 파일에서 서비스 워커를 초기화하여 MSW가 설정된 모든 API 요청을 가로챌 수 있도록 구성합니다. 이 설정은 개발 환경에서만 동작하도록 하여 프로덕션 빌드에서는 MSW가 동작하지 않도록 할 수 있습니다.
// src/main.tsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.tsx';
async function enableMocking() {
if (import.meta.env.DEV === false) return;
const { worker } = await import('./mocks/browser');
// 서비스 워커를 시작하여 API 요청을 모킹
return worker.start();
}
// MSW 설정을 활성화한 후 애플리케이션을 렌더링
enableMocking().then(() => {
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>
);
});
이 구성은 import.meta.env.DEV 조건을 사용하여 개발 환경에서만 MSW를 활성화합니다. worker.start() 메서드는 서비스 워커가 준비되면 API 요청을 가로채도록 설정하며, 이후 App 컴포넌트를 렌더링합니다.
이제 애플리케이션의 모든 API 요청이 MSW에 의해 처리되며, 설정된 모킹 데이터가 반환됩니다. 이를 통해 네트워크 의존성 없이도 프론트엔드 개발과 테스트를 안정적으로 수행할 수 있습니다.
이렇게 main.tsx 파일을 통해 서비스 워커를 설정하고 애플리케이션에 적용하면, MSW가 자동으로 요청을 가로채 모킹된 응답을 반환하여 다양한 테스트 시나리오를 구성할 수 있습니다.
@mswjs/data
@mswjs/data는 MSW와 함께 사용하는 데이터 관리 라이브러리로, 간단한 모킹 데이터베이스를 구축하여 CRUD 작업을 가능하게 합니다. @mswjs/data를 사용하면 상태를 변화시키며 테스트할 수 있어, 실제 서버 없이도 CRUD 작업을 시뮬레이션하는 것이 가능합니다.
@mswjs/data의 주요 기능
간편한 데이터 모델 정의
테이블과 필드를 설정하여 다양한 데이터 모델을 쉽게 정의할 수 있습니다.
CRUD 작업 지원
기본적인 데이터 조작(Create, Read, Update, Delete) 기능을 제공하여 모킹 데이터의 실시간 변화를 관리할 수 있습니다.
가상 데이터 관리
다양한 요청에 맞춰 데이터를 조회, 생성, 수정, 삭제할 수 있어 더욱 정교한 테스트가 가능합니다.
mswjs/data 설치
npm install @mswjs/data --save-dev
예제: Todo API 모킹
이 예제에서는 MSW와 @mswjs/data를 사용하여 Todo API 요청을 모킹합니다. 모킹된 API는 클라이언트의 CRUD 요청을 처리하고, 각각의 데이터 조작에 대해 적절한 응답을 반환합니다.
src/
├── mocks/
│ ├── browser.ts # MSW 서비스 워커 설정 파일
│ ├── db.ts # @mswjs/data로 데이터베이스 생성
│ └── handlers/
│ └── todoHandlers.ts # Todo API 핸들러 정의
└── index.tsx # MSW 초기화
1. 서비스 워커 설정 (browser.ts)
서비스 워커를 설정하여, 정의한 모든 요청을 가로챌 수 있도록 합니다.
// src/mocks/browser.ts
import { setupWorker } from 'msw/browser';
import todoHandlers from './handlers/todoHandlers';
export const worker = setupWorker(...todoHandlers);
setupWorker 함수는 브라우저 환경에서 동작하는 서비스 워커를 설정하는 역할을 합니다. todoHandlers는 이후에 정의할 Todo API 핸들러 목록입니다.
2. 데이터베이스 설정 (db.ts)
@mswjs/data의 factory 함수를 통해 데이터 모델을 설정합니다. 여기서는 todo 테이블에 id, title, completed 필드를 정의했습니다.
// src/mocks/db.ts
import { factory, primaryKey } from '@mswjs/data';
const db = factory({
todo: {
id: primaryKey(Number),
title: String,
completed: Boolean,
},
});
// 초기 데이터 생성
db.todo.create({ id: 1, title: 'Learn Intersection Observer API', completed: true });
db.todo.create({ id: 2, title: 'Learn Tanstack Query', completed: true });
db.todo.create({ id: 3, title: 'Learn CSS z-index priority', completed: true });
db.todo.create({ id: 4, title: 'Learn Next.js prevent navigation', completed: true });
db.todo.create({ id: 5, title: 'Learn Mutation Observer API', completed: true });
db.todo.create({ id: 6, title: 'Learn manage modal efficiently', completed: true });
db.todo.create({ id: 7, title: 'Learn @mswjs/data', completed: false });
db.todo.create({ id: 8, title: 'Learn MSW', completed: false });
export default db;
db.todo.create 메서드를 통해 초기 데이터를 생성하여, 모킹된 데이터베이스에서 Todo 데이터를 조회하고 테스트할 수 있습니다.
3. Todo API 핸들러 정의 (todoHandlers.ts)
// src/mocks/handlers/todoHandlers.ts
import { http, HttpResponse } from 'msw';
import db from '../db';
export interface TodoItem {
id: number;
title: string;
completed: boolean;
}
const todoHandlers = [
// 모든 할 일 가져오기
http.get('/api/todos', ({ request }) => {
const url = new URL(request.url);
const searchParams = new URLSearchParams(url.search);
const page = Number(searchParams.get('page')) || 1;
const pageSize = Number(searchParams.get('pageSize')) || 5;
const todos = db.todo.findMany({
take: pageSize,
skip: (page - 1) * pageSize,
orderBy: {
id: 'desc',
},
});
const totalPages = Math.ceil(db.todo.count() / pageSize);
return HttpResponse.json({
pagination: {
totalItems: db.todo.count(),
pageSize,
totalPages,
currentPage: page,
hasNextPage: page < totalPages,
hasPreviousPage: page > 1,
},
data: todos,
});
}),
// 새로운 할 일 생성하기
http.post('/api/todos', async ({ request }) => {
const body = (await request.json()) as { title: string };
const id = db.todo.count() + 1;
const todo = db.todo.create({
id,
title: body.title,
completed: false,
});
return HttpResponse.json(todo, { status: 201 });
}),
// 특정 할 일 업데이트하기
http.patch('/api/todos/:id', async ({ params, request }) => {
const id = Number(params.id);
const body = (await request.json()) as { completed: boolean };
const todo = db.todo.findFirst({
where: {
id: {
equals: id,
},
},
});
if (!todo) return HttpResponse.error();
const updatedTodo = db.todo.update({
where: {
id: {
equals: id,
},
},
data: {
completed: body.completed,
},
});
return HttpResponse.json(updatedTodo);
}),
// 특정 할 일 삭제하기
http.delete('/api/todos/:id', async ({ params }) => {
const id = Number(params.id);
const todo = db.todo.findFirst({
where: {
id: {
equals: id,
},
},
});
if (!todo) return HttpResponse.error();
db.todo.delete({
where: {
id: {
equals: id,
},
},
});
return HttpResponse.json({ id });
}),
];
export default todoHandlers;
이제, todoHandlers를 통해 Todo API 요청에 대한 모킹이 설정되었습니다. 클라이언트가 이를 호출할 때마다 설정된 응답이 반환되며, 데이터베이스 상태에 맞춰 요청이 처리됩니다.
정리
MSW와 @mswjs/data를 사용하여 클라이언트 측에서 모킹된 API와 데이터베이스를 쉽게 구축할 수 있습니다. 이를 통해 네트워크 의존성을 배제하고 일관된 데이터 환경에서 프론트엔드 개발을 독립적으로 진행할 수 있으며, 다양한 에러와 네트워크 상황을 쉽게 시뮬레이션할 수 있습니다.