1. msw의 역할과 장점1-1. 역할1-2. 장점1-2-1. HTTP 라이브러리 무관1-2-2. 쉽고 단순하게도, 서버 로직을 담을 정도로 복잡하게도 가능한 mock 방식2. mock 레시피2-1. 입력, 출력 타이핑하기2-2. 특정 Object를 반환하기2-3. 일정 시간 기다린 후 응답하기2-4. 오류로 응답하기2-4-1. statusCode만 주기2-4-2. JSON body를 주기2-4-3. Network 오류로 응답하기3. 설정 방법3-1. React 개발 환경에서 Mock 적용하기3-2. API 종류 별로 핸들러 관리하기 3-3. 전역 delay 적용하기3-4. 특정 Request들 알림 무시하기4. 설치 방법
1. msw의 역할과 장점
1-1. 역할
msw는 브라우저에서 출발하는 네트워크 요청을 가로채 대신 응답을 하는 도구에요.
주로 mock data를 제공하는데 사용해요.
1-2. 장점
1-2-1. HTTP 라이브러리 무관
API를 직접 요청하는 컴포넌트는 테스트하기 위해서는 Ajax 요청 수준에서 데이터를 변조해야 돼요.
두 가지 방법이 있어요:
- 해당 라이브러리의 함수를 mocking
- 예시: axios mock
- HTTP 수준에서 mocking
- 예시: msw
msw로 mocking하는 경우 프론트엔드에서 어떤 도구로 HTTP 요청을 하는지는 무관해요.
HTTP 수준에서 응답을 제공하기 때문에요.
이후 프로젝트의 의존성을
axios
등에서 ky
등으로 변경한다면 axios-mock
등을 사용해 작성한 코드들은 모두 변환해주어야 하지만, msw
로 작성한 코드는 그대로 놔둘 수 있어요.1-2-2. 쉽고 단순하게도, 서버 로직을 담을 정도로 복잡하게도 가능한 mock 방식
항상 동일한 객체를 반환하는 mock은 만들기 쉬워요.
http.get('/api/v1/notifications', async () => HttpResponse.json([ { id: 1, type: '리뷰 도착', createdAt: '10분 전', link: '/me', message: '누군가 회원님에 대한 리뷰를 남겨주셨어요.', } ]);
백엔드 핸들러 수준의 mock도 만들 수 있어요.
http.post('/api/v1/users/:userId', async ({ params, request }) => { // Path Param이에요. const { userId } = params; // JSON body에요. const { param1, param2 } = await request.json(); // FormData에요. Map으로 사용해요. const formData = await request.formData(); formData.get('name'); // QueryString이에요. const url = new URL(request.url) // "?id=1&id=2&id=3" const ids = url.searchParams.getAll('id') // ["1", "2", "3"] return /* ... */; });
2. mock 레시피
2-1. 입력, 출력 타이핑하기
http.get|post|...
에 제네릭으로 PathParam, RequestJSON, ReseponseJSON을 타입으로 줄 수 있어요.http.post< PathParamType, RequestJSONType, ResponseJSONType >("/api/v1/users/:userId", async ({ params, request }) => { // Path Param이에요. 제네릭으로 입력한 Type이 돼요. const { userId } = params; // JSON body에요. 제네릭으로 입력한 Type이 돼요. const { param1, param2 } = await request.json(); }),
2-2. 특정 Object를 반환하기
http.get(API_URL, async () => HttpResponse.json([ { id: 1, type: '리뷰 도착', createdAt: '10분 전', link: '/me', message: '누군가 회원님에 대한 리뷰를 남겨주셨어요.', }, ]);
2-3. 일정 시간 기다린 후 응답하기
http.get(API_URL, async () => { await delay(DELAY_IN_MILLIS); return HttpResponse.json([ { id: 1, type: '리뷰 도착', createdAt: '10분 전', link: '/me', message: '누군가 회원님에 대한 리뷰를 남겨주셨어요.', }, ]); }),
2-4. 오류로 응답하기
2-4-1. statusCode만 주기
http.get(API_URL, async () => { throw HttpResponse.json(undefined, { status: 500, }); });
2-4-2. JSON body를 주기
http.get(API_URL, async () => { throw HttpResponse.json( { message: '이게 문제고 저게 문제에요', }, { status: 400, }, ); });
2-4-3. Network 오류로 응답하기
Axios 기준으로
ERR_NETWORK
코드로 반환해요.http.get(API_URL, async () => { throw HttpResponse.error(); });
3. 설정 방법
3-1. React 개발 환경에서 Mock 적용하기
msw 초기화 과정은 비동기이기 때문에
await
으로 기다려줘야 해요.초기화가 완료되면
createRoot
가 실행되게 코드를 작성해주셔야 돼요.// main.tsx (async () => { if (import.meta.env.DEV) { await enableAPIMocks(); } ReactDOM.createRoot(document.getElementById('root')!).render( <StrictMode> <App /> </StrictMode>, ); })();
3-2. API 종류 별로 핸들러 관리하기
같은 종류의 API끼리는 파일 단위로 분리한 후
handlers
에서 합치면 간결하게 관리할 수 있어요.setUpWorker
의 인자인 handlers
에 들어간 모든 핸들러들이 msw에 등록되기 때문에, 비구조화 할당으로 풀어서 넣으면 돼요.예시 폴더 구조
src/ mocks/ index.ts notifications.ts (...)
예시 index.tsx
// mocks/index.tsx import { RequestHandler, SharedOptions } from 'msw'; import { setupWorker } from 'msw/browser'; import { notificationsMocks } from './notifications'; export const enableAPIMocks = () => { // 이곳에 다른 API mock들도 풀어 넣으면 돼요. const handlers: RequestHandler[] = [ ...notificationsMocks ]; const worker = setupWorker(...handlers); return worker.start({ onUnhandledRequest: warnButIgnoreResources, }); }; // 개발 리소스들은 무시해요 const PATHS_IGNORED_BY_MSW = ['/src', '.vite', 'favicon', 'fonts']; type OnUnHandledRequest = SharedOptions['onUnhandledRequest']; const warnButIgnoreResources: OnUnHandledRequest = (req, print) => { if (PATHS_IGNORED_BY_MSW.find(path => req.url.includes(path))) { return; } print.warning(); };
3-3. 전역 delay 적용하기
전역적으로 delay를 적용해줄 수도 있어요.
delay가 없으면 실제 API보다 너무 일찍 반환하기 때문에 적용해주면 좋아요.
export const handlers = [ // 가장 첫 핸들러로 등록해요 http.all('*', async () => { await delay() // millis를 입력하지 않으면 자연스러운 서버 지연 시간을 제공 }), ...notificationMocks, ]
3-4. 특정 Request들 알림 무시하기
msw로 모든 요청이 통과하기 때문에, unhandle 되었다는 알림이 Console을 채우게 돼요.
이를 해결하기 위해
worker.start
호출 시 옵션으로 onUnhandledRequest
콜백 함수를 전달할 수 있어요.export const enableAPIMocks = () => { const handlers: RequestHandler[] = [...notificationsMocks]; const worker = setupWorker(...handlers); return worker.start({ onUnhandledRequest: ignoreDevResources, }); }; const PATHS_IGNORED_BY_MSW = [ '/src', '/virtual', '.vite', '.json', 'favicon', 'fonts', ]; type OnUnHandledRequest = SharedOptions['onUnhandledRequest']; export const ignoreDevResources: OnUnHandledRequest = (req, print) => { if (PATHS_IGNORED_BY_MSW.find(path => req.url.includes(path))) { return; } print.warning(); };
4. 설치 방법
설치 이후
init
명령을 실행시켜주세요.pnpm install -D msw pnpm msw init /public
아래와 유사한 내용의
mockServiceWorker.js
파일이 생기면 잘 설치된 거에요./* eslint-disable */ /* tslint:disable */ /** * Mock Service Worker (2.1.7). * @see https://github.com/mswjs/msw * - Please do NOT modify this file. * - Please do NOT serve this file on production. */ const INTEGRITY_CHECKSUM = '223d191a56023cd36aa88c802961b911' const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') const activeClientIds = new Set() ...