pnpm i msw -D
-D 옵션은 이 패키지가 개발 환경에서만 필요한 의존성임을 명시한다.https://mswjs.io/docs/quick-start
npx msw init ./public --save
mockServiceWorker.js라는 파일을 생성한다.mockServiceWorker.js를 통해 앱의 네트워크 요청을 감시하고 가로챌 수 있다../public 폴더에 생성하는 이유는 대부분의 프레임워크에서 이 폴더를 정적 파일 루트로 사용하므로, 브라우저가 해당 주소로 쉽게 접근할 수 있기 때문이다.src/mocks/handler.ts
// success, loading, error 케이스 작성
import { delay, http, HttpResponse } from 'msw'
import mock from './mock.json'
// 성공 케이스: 요청 시 즉시 mock.json 데이터를 반환
export const success = http.get('https://pokeapi.co/api/v2/pokemon?limit=10&offset=0', () => {
return HttpResponse.json(mock)
})
// 로딩 케이스: 요청 시 99999ms 동안 지연시킨 후 응답 (테스트용)
export const loading = http.get('https://pokeapi.co/api/v2/pokemon?limit=10&offset=0', async () => {
await delay(99999)
})
// 실패 케이스: 요청 시 즉시 에러 상태를 반환
export const error = http.get('https://pokeapi.co/api/v2/pokemon?limit=10&offset=0', () => {
return HttpResponse.error()
})
어떤 API 요청을 가로챘을 때, 어떤 가짜 응답을 돌려줄지 규칙을 정의한다.
만약 GET 요청이 이 주소(https://pokeapi.co/...)로 오면, 이렇게(HttpResponse.json(mock)) 응답해 라고 알려주는 시나리오 대본과 같다.
src/mocks/mock.json
{
"count": 20000,
"next": "https://pokeapi.co/api/v2/pokemon?offset=10&limit=10",
"previous": null,
"results": [
{
"name": "bulbasaur",
"url": "https://pokeapi.co/api/v2/pokemon/1/"
}
]
}
src/main.tsx
import { setupWorker } from "msw/browser";
import { success } from "./mocks/handler.ts";
const enableMSW = async (sign: "stop" | "start") => {
const worker = setupWorker(success); // 3단계에서 만든 핸들러를 워커에 등록
await worker[sign](); // 'start' 또는 'stop' 명령 실행
};
setupWorker 함수에 요청 핸들러들을 등록하여 워커 인스턴스를 생성하고, 이 워커를 시작(start)하거나 중지(stop)하는 enableMSW 함수를 만든다.
pnpm i @tanstack/react-query
API 요청 라이브러리인 @tanstack/react-query를 설치한다.
src/main.tsx
import { QueryClient, QueryClientContext } from "@tanstack/react-query";
enableMSW("start").then(() => {
createRoot(document.getElementById("root")!).render(
<StrictMode>
<QueryClientContext value={new QueryClient()}>
<App />
</QueryClientContext>
</StrictMode>
);
});
enableMSW("start")를 호출하여 MSW를 활성화하고, 활성화가 완료된 후에 React 애플리케이션을 렌더링한다.
enableMSW("start")가 반환하는 Promise가 완료된 후에 앱을 렌더링하는 .then() 구조이다.
만약 MSW가 준비되기 전에 <App /> 컴포넌트가 렌더링되면, 컴포넌트 내부에서 발생하는 초기 API 요청을 MSW가 가로채지 못하고 실제 네트워크로 요청이 날아갈 수 있다.
.then()을 사용해 MSW가 요청을 가로챌 준비를 마치면, 그때 앱을 실행시킬 수 있도록 순서를 보장하는 것이다.
<App /> 컴포넌트에서 @tanstack/react-query의 useQuery 훅을 사용해 API를 호출
// App.tsx
import "./App.css";
import { useQuery } from "@tanstack/react-query";
function App() {
const { data, isLoading, isError } = useQuery({
queryKey: ["pokeList"],
queryFn: () =>
// 이 fetch 요청이 바로 MSW의 가로채기 대상이 된다.
fetch("https://pokeapi.co/api/v2/pokemon?limit=10&offset=0")
.then((res) => res.json()),
});
// MSW의 'loading' 핸들러가 활성화되면 이 UI가 보인다.
if (isLoading) {
return <>로딩 상태</>;
}
// MSW의 'error' 핸들러가 활성화되면 이 UI가 보인다.
if (isError) {
return <>에러 상태</>;
}
// MSW의 'success' 핸들러가 활성화되면 가짜 데이터(mock.json)가 보인다.
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}
export default App;

개발자 도구 네트워크 탭에 요청이 (from ServiceWorker) 라고 표시되는 것은 해당 네트워크 요청이 실제 서버로 전송된 것이 아니라, 브라우저에 설치된 서비스 워커(Service Worker)에 의해 가로채여 처리되었음을 의미한다.
MSW(Mock Service Worker)가 바로 이 서비스 워커를 사용해 작동한다.
요청 가로채기(Intercept): App.tsx에서 fetch 함수를 호출하면, 이 요청은 실제 인터넷으로 나가기 전에 먼저 브라우저의 서비스 워커로 전달된다.
모의 응답(Mock Response): MSW가 설치한 서비스 워커는 들어온 요청의 URL을 확인하고, 미리 정의된 핸들러(예: success, error 핸들러)와 일치하는 것이 있는지 찾는다.
응답 반환: 일치하는 핸들러를 찾으면, 실제 네트워크 통신 없이 핸들러에 정의된 가짜 데이터(mock response)를 즉시 반환한다.
결론적으로, 개발자 도구의 (from ServiceWorker) 표시는 MSW가 성공적으로 활성화되어 API 요청을 가로채고 가짜 응답을 잘 내려주고 있다는 증거이다.
이를 통해 실제 백엔드 서버 없이도 프론트엔드 개발 및 테스트를 원활하게 진행할 수 있다.