[React] MSW로 API Mocking 하기
개발 초기, 자신이 프론트엔드라면 백엔드로부터 API가 아직 준비되지 않았거나 외부 API 호출에 의존적이라면 개발 속도가 느려질 수 있다.
API 모킹을 통해 실제 백엔드 서비스 없이도 프론트엔드 애플리케이션의 동작을 시뮬레이션할 수 있기 때문에 MSW는 매우 유용하다.
이럴 때 MSW(Mock Service Worker)
를 사용하면, 실제 네트워크 요청을 가로채서 원하는 응답을 만들어주는 가짜 서버 환경을 구성할 수 있어 매우 유용하다.
해당 내용은 MSW 공식문서를 참고하여 작성하였다.
이번 포스트에서는 Vite + React 환경에서 MSW를 적용하는 방법을 자세히 알아보자.
MSW란?
MSW(Mock Service Worker)
는 브라우저의 Service Worker API를 활용해 실제 네트워크 요청을 가로채고, 우리가 정의한 가짜 응답(mock response)으로 대체해주는 API Mocking 라이브러리이다.
MSW를 언제 사용해야 할까?
- 백엔드 API가 아직 완성되지 않음
- 팀 내 프론트/백엔드 개발 속도 불일치
- 외부 API에 의존성이 있어 개발 중단
- 테스트 환경에서 실제 API 호출이 부담됨
위와 같은 문제를 해결하기 위한 대안 중 가장 이상적인 도구가 바로 MSW(Mock Service Worker)
이다.
MSW의 주요 장점
- 브라우저에서 실제 네트워크 요청처럼 작동 (fetch/XHR 모두 지원)
- API 레벨에서 요청을 가로채기 때문에 UI는 그대로 테스트 가능
- 개발, 테스트, 스토리북 환경 모두 지원
- REST뿐 아니라 GraphQL도 지원
MSW 세팅하기
1. MSW 설치
npm install msw@latest --save-dev
2. Service Worker 파일 생성
MSW는 Service Worker를 사용하여 네트워크 요청을 가로채기 때문에, 먼저 Service Worker 파일을 생성해야 한다.
아래 명령어로 MSW의 서비스 워커 파일을 생성한다.
이 명령은 public/mockServiceWorker.js 파일을 생성한다.
- public/ 폴더에 넣는 이유: 이 폴더 안의 파일은 Vite에서 루트 경로(/)에 그대로 노출된다.
- 생성된 파일은 자동으로 /mockServiceWorker.js 경로에서 접근 가능해진다.
npx msw init public --save
서비스 워커 파일을 생성한 후에는 반드시 Vite dev 서버를 다시 시작하는 것이 좋다.
MSW 폴더 구조
MSW를 사용하기 위해 필요한 구성은 다음과 같다.
본인은 dummy.json 파일을 사용하여 더미 데이터를 만들고, handlers.ts에서 API 모킹을 정의하였다.
src/
├── mocks/
├── ├── dummy.json // 더미 데이터
│ ├── handlers.ts // API 모킹 정의
│ ├── browser.ts // 브라우저용 MSW 설정
│ └── server.ts // 테스트용 MSW 설정 (Node 환경)
├── setupTests.ts // 테스트 환경 초기화
└── main.tsx // 개발 중 MSW 실행
handlers.ts
handlers.ts
는 어떤 요청이 들어오면 어떤 응답을 줄지를 정의하는 곳이다.
- 📄 src/mocks/dummy.ts
[
{
"user": "Levi",
"role": "FE"
},
{
"user": "Kevin",
"role": "BE"
},
{
"user": "Bree",
"role": "Designer"
}
]
- 📄 src/mocks/handlers.ts
import { HttpResponse, http } from 'msw';
import dummy from './dummy.json';
export const handlers = [
http.get('/api/user', () => {
return HttpResponse.json(dummy);
}),
];
browser.ts
browser.ts
는 브라우저 환경에서 MSW를 설정하는 곳이다. setupWorker
를 사용하여 Service Worker를 등록하고, handlers.ts
에서 정의한 핸들러를 연결한다.
- 📄 src/mocks/browser.ts
import { setupWorker } from 'msw/browser';
import { handlers } from './handlers';
export const worker = setupWorker(...handlers);
개발 환경에서 MSW 실행
- 📄 src/main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
async function enableMocking() {
if (process.env.NODE_ENV !== "development") {
return;
}
const { worker } = await import("./mocks/browser"); // Dynamic import
return worker.start();
}
enableMocking().then(() => {
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
});
위 코드는 개발(dev) 환경에서만 MSW를 실행하도록 설정한다.
정상적으로 MSW를 통해 Mocking이 완료되면, 위와 같이 브라우저의 개발자 도구에서 Console 탭에서 성공한 것을 확인할 수 있다.
이제 React 컴포넌트에서 실제 사용 예시를 살펴보자.
- 📄 src/App.tsx
import { useEffect, useState } from 'react';
function App() {
const [user, setUser] = useState(null);
useEffect(() => {
fetch('/api/user')
.then((res) => res.json())
.then(setUser);
}, []);
return (
<div>
<h1>사용자 정보</h1>
{user ? (
<p>
{user.name} ({user.role})
</p>
) : (
<p>로딩 중...</p>
)}
</div>
);
}
export default App;
위 코드와 같이 /api/user
엔드포인트에 GET 요청을 보내면, MSW
가 /api/user 요청을 가로채고, 따로 정의한 JSON을 응답합니다.
테스트 환경에서 MSW 실행
MSW
는 테스트에서도 동일한 요청 가로채기를 제공한다.
테스트 환경에서는 setupServer
를 사용하여 Node.js 환경에서 MSW를 설정한다.
테스트 환경에서 MSW를 실행하는 방법을 소개하기 앞서, React에서 React Testing Library (RTL)를 먼저 설치하여 준비해보자.
npm install --save-dev @testing-library/react @testing-library/dom
Typescript를 사용하는 경우, 아래와 같이 타입 정의도 설치해준다.
@types/react @types/react-dom
server.ts
- 📄 src/mocks/server.ts
import { setupServer } from 'msw/node';
import { handlers } from './handlers';
export const server = setupServer(...handlers);
server.ts
는 Node.ts 환경에서 MSW를 설정하는 곳이다. setupServer
를 사용하여 서버를 설정하고, handlers.ts
에서 정의한 핸들러를 연결한다.
- 📄 src/setupTests.ts
import { server } from './mocks/server';
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
- 📄 vite.config.ts 테스트 설정 추가
import { defineConfig } from 'vitest/config'; // vitest 전용 설정
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'jsdom',
setupFiles: './src/setupTests.ts',
},
});
- 📄 src/App.test.tsx
import { render, screen, waitFor } from '@testing-library/react';
import App from './App';
test('사용자 정보를 렌더링한다', async () => {
render(<App />);
await waitFor(() => {
expect(screen.getByText(/프론트엔드 개발자/)).toBeInTheDocument();
});
});
오류가 발생했을 경우
Uncaught (in promise) Error: [MSW] Failed to register a Service Worker for scope ('https://your-github-id.github.io/') with script ('https://your-github-id.github.io/mockServiceWorker.js'): Service Worker script does not exist at the given path.
Did you forget to run "npx msw init <PUBLIC_DIR>"?
Learn more about creating the Service Worker script: https://mswjs.io/docs/cli/init
at getWorkerInstance (index-CW2z-yh0.js:25297:13)
at async startWorkerInstance (index-CW2z-yh0.js:26227:24)
at async SetupWorkerApi.start (index-CW2z-yh0.js:27870:12)
혹시 개발자도구의 네트워크탭에서 위와 같은 에러가 발생한다면, public/mockServiceWorker.js
파일이 존재하는지 확인해보자.
있음에도 불구하고 여전히 같은 에러가 발생한다면,
vite.config.ts 파일의 defineConfig에 아래 코드와 같이 base와 test를 추가했는지 확인해보자.
- vite.config.ts
export default defineConfig(({ mode }) => ({
plugins: [react()],
base: mode === 'production' ? '/react-shopping-products/' : '/',
test: {
globals: true,
environment: 'jsdom',
},
}));
위 설정에서,
globals: true
는 describe
, it
, expect
, beforeEach
같은 테스트 함수들을 전역(global) 으로 사용할 수 있게 한다.
그리고 environment: 'jsdom'
은 테스트가 실행될 환경을 브라우저처럼 흉내 내는 jsdom으로 설정한다.
jsdom
은 브라우저의 window
, document
, HTMLElement
, localStorage
같은 객체를 Node.js
환경에서 에뮬레이션한다.
Vite가 빌드한 리소스들의 경로를 base
에 맞게 조정한다.
vite build
시 HTML 내 경로가 /react-shopping-products/
를 기준으로 생성된다
따라서 실제 빌드 결과가 GitHub Pages에서 정확히 작동하게 된다.
- main.tsx
async function enableMocking() {
// if (process.env.NODE_ENV !== 'development') {
// return;
// }
const isLocalhost = location.hostname === 'localhost';
const { worker } = await import('./mocks/browser');
return worker.start({
serviceWorker: {
url: isLocalhost ? '/mockServiceWorker.js' : '/react-shopping-products/mockServiceWorker.js',
},
onUnhandledRequest: 'bypass',
});
}
enableMocking().then(() => {
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
});
위 설정은 개발 환경과 배포 환경에 따라 서비스 워커의 위치를 다르게 지정한다.
mockServiceWorker.js
는 public/
디렉토리에 들어 있고, vite.config.ts
에서 base
를 설정했기 때문에 배포 시엔 다음과 같은 경로에 위치한다.
마무리
MSW는 단순한 모킹 도구를 넘어서, 프론트엔드 개발자가 API와 독립적으로 개발하고 테스트할 수 있는 강력한 도구이다.
Vite + React 환경에서는 구성도 간단하며, 개발/테스트/스토리북 등 모든 상황에 대응 가능하다.
이 포스트를 통해 MSW의 기본적인 사용법을 익혔으니, 실제 프로젝트에 잘 적용해보길 바란다.