백엔드 API 개발이 늦어져 프론트엔드 화면 구현이나 단위 테스트를 진행하지 못하고 무작정 기다린 경험이 있을 것이다.
다만 정식 API가 나올 때까지 컴포넌트 내부에 임시 데이터를 하드코딩하거나 fetch 함수를 직접 수정하는 방식은 나중에 코드를 정리할 때 버그를 유발하기 쉽다.
이번에는 네트워크 계층에서 실제 HTTP 요청을 가로채는 MSW(Mock Service Worker) v2가 정확히 뭔지, 왜 필요한지, 그리고 실무 테스트 환경에 어떻게 적용하는지 완벽하게 정리해서 소개하겠다.
MSW는 브라우저 및 Node.js 환경에서 서비스 워커(Service Worker)를 활용해 실제 네트워크 요청을 가로채고 가짜 응답(Mock Response)을 반환하는 라이브러리다.
기존의 단순 모킹 방식은 fetch나 axios 같은 HTTP 클라이언트 모듈 자체를 덮어씌워 테스트를 진행했다.
반면 MSW는 브라우저의 서비스 워커 레이어에서 동작하므로, 애플리케이션 코드를 단 한 줄도 수정하지 않고 실제 서버와 통신하는 것과 동일한 환경을 만들어준다.
다음은 대표적인 프론트엔드 API 모킹 방식 간의 비교다.
| 비교 항목 | 코드 하드코딩 | mockServer(json-server) | MSW v2 (Service Worker) |
|---|---|---|---|
| 코드 오염도 | 높음 (실제 코드 수정 필요) | 없음 | 없음 (네트워크 계층 가로채기) |
| 포트/서버 실행 | 불필요 | 별도 로컬 서버 실행 필요 | 불필요 (브라우저 스레드 실행) |
| 네트워크 탭 확인 | 불가능 | 가능 | 가능 (실제 HTTP 요청으로 표시) |
| 테스트 연동 | 어려움 | 네트워크 대기 시간 발생 | Node.js 환경(Vitest/Jest) 즉시 연동 |
MSW v2 버전은 기존 v1과 비교해 핸들러 정의 방식과 응답 객체 포맷이 전면 개편되었다.
우선 프로젝트 패키지 관리자를 통해 MSW를 설치하고 서비스 워커 스크립트를 생성해야 한다.
터미널에서 아래 명령어로 MSW를 설치하고, 퍼블릭 디렉토리에 서비스 워커 워커 파일(mockServiceWorker.js)을 생성해준다.
npm install msw@latest --save-dev
npx msw init public/ --save이어서 핸들러 파일(handlers.js)을 작성하여 가로챌 API 엔드포인트를 정의한다.
MSW v2에서는 http 객체와 HttpResponse 클래스를 사용해 응답을 구성한다.
// src/mocks/handlers.js
import { http, HttpResponse } from 'msw';
export const handlers = [
// GET 요청 모킹
http.get('/api/users/1', () => {
return HttpResponse.json({
id: 1,
name: '홍길동',
email: 'hong@example.com'
});
}),
// POST 요청 모킹 및 요청 바디 검증
http.post('/api/users', async ({ request }) => {
const newUser = await request.json();
return HttpResponse.json(
{ id: Date.now(), ...newUser },
{ status: 201 }
);
})
];마지막으로 브라우저 환경에서 서비스 워커를 시작하도록 설정 코드를 추가한다.
// src/mocks/browser.js
import { setupWorker } from 'msw/browser';
import { handlers } from './handlers';
export const worker = setupWorker(...handlers);
개발 모드일 때 MSW 워커를 실행하고 실제 컴포넌트에서 fetch 요청을 보내는 흐름을 확인해본다.
✗ 잘못된 코드 (컴포넌트에 모킹 로직 침범)
// UserProfile.js - 실제 코드에 모킹 조건문이 들어가서 배포 시 위험함
export async function getUserProfile(userId) {
if (process.env.NODE_ENV === 'development') {
// 가짜 데이터를 직접 반환하여 네트워크 통신 흐름을 검증할 수 없음
return { id: userId, name: '임시 사용자' };
}
const response = await fetch(`/api/users/${userId}`);
return response.json();
}✓ 올바른 코드 (MSW를 통한 순수 애플리케이션 코드 유지)
// UserProfile.js - 모킹 여부와 관계없이 일관된 표준 fetch 사용
export async function getUserProfile(userId) {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error('사용자 정보를 불러오는데 실패했습니다.');
}
return response.json();
}
// index.js - 애플리케이션 진입점에서 개발 환경일 때만 MSW 워커 구동
async function prepareApp() {
if (process.env.NODE_ENV === 'development') {
const { worker } = await import('./mocks/browser');
await worker.start({ onUnhandledRequest: 'bypass' });
}
}
prepareApp().then(() => {
// 앱 리액트/뷰 마운트 로직 실행
});실행 결과 및 개발자 도구 출력 :
브라우저 개발자 도구 콘솔에 `[MSW] Mocking enabled.` 메세지가 출력된다.
네트워크 탭을 확인해보면 `/api/users/1` 요청이 200 OK 응답으로 수신되며, MSW가 가로채서 반환한 JSON 데이터가 그대로 전달되는 것을 확인할 수 있다.
MSW v2를 도입할 때 개발자들이 흔히 겪는 오류 패턴 두 가지를 짚어보겠다.
✗ 잘못된 방식: v1 스타일인 `rest.get`, `res(ctx.json())` 문법 사용
// MSW v2에서는 rest 객체가 폐지되었습니다.
import { rest } from 'msw';
export const handlers = [
rest.get('/api/test', (req, res, ctx) => res(ctx.json({ ok: true })))
];✓ 올바른 방식: v2 스타일인 `http.get`, `HttpResponse.json()` 적용
import { http, HttpResponse } from 'msw';
export const handlers = [
http.get('/api/test', () => HttpResponse.json({ ok: true }))
];개발 서버 실행 시 브라우저 콘솔에 `[MSW] Failed to register a ServiceWorker` 에러가 발생하는 경우가 많다.
이는 `npx msw init <PUBLIC_DIR>` 명령을 실행하지 않았거나, 프레임워크의 public 디렉토리 경로가 맞지 않아 `mockServiceWorker.js` 파일에 접근할 수 없을 때 발생한다.
React(Vite)는 `public/`, Next.js는 `public/` 디렉토리에 해당 파일이 정상적으로 등록되어 있는지 확인해야 한다.
MSW v2는 백엔드 API 개발 속도에 구애받지 않고 프론트엔드 개발과 단위 테스트를 독립적으로 진행할 수 있게 해주는 핵심 도구다.
네트워크 레이어를 직접 가로채는 올바른 모킹 습관이 모여서 개발 생산성과 테스트 신뢰도를 극대화한다는 점을 잊지 말자.
이 글의 MSW v2 설정 단계와 핸들러 구성법을 참고해 실무 프로젝트에 적용하면, API 미완성으로 인한 개발 병목을 완벽히 해결할 수 있을 것이다.