예시로 배우는 msw API mocking

예시로 배우는 msw API mocking

Tags
msw
테스팅
Published
February 26, 2024
Author
Seongbin Kim
 

1. msw의 역할과 장점

 

1-1. 역할

msw는 브라우저에서 출발하는 네트워크 요청을 가로채 대신 응답을 하는 도구에요.
주로 mock data를 제공하는데 사용해요.
 

1-2. 장점

 

1-2-1. HTTP 라이브러리 무관

API를 직접 요청하는 컴포넌트는 테스트하기 위해서는 Ajax 요청 수준에서 데이터를 변조해야 돼요.
두 가지 방법이 있어요:
  • 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(); });
notion image
 

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을 채우게 돼요.
notion image
 
이를 해결하기 위해 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() ...