
mswjs를 알게 된 건 같이 프로젝트를 진행하던 팀원이 mswjs를 사용해보자고 해서 사용하게 되었다.
처음에는 간단하게 json-server를 사용하려고 했으나 보다 더 많은 기능과 특히 mock api server를 개발자 입맛에 맞게 어느정도 커스텀이 가능하다는 점에서 채택하게 되었다.
mock은 사전적인 의미로 모조품을 뜻하며 mswjs docs에서 이와 같이 소개한다.
Mock Service Worker is an API mocking library that allows you to write client-agnostic mocks and reuse them across any frameworks, tools, and environments.
MSW는 Mock Service Worker의 약자로 어느 도구, 환경 및 클라이언트 프레임워크에 상관없이 재사용 가능한 모조품을 만들게 해주는 API Mocking Library라 한다.
🤔 mocking이란??
실제 API를 호출하는 대신 가상의 API 호출을 시뮬레이션하는 기술을 뜻하여 개발 중 또는 테스트할 때 유용하다. (e.g json-server)
간단하게 말해서 가상의 API 호출을 담당하는 서버를 구축할 수 있다는 것이다.
프로젝트가 돌아가는 환경은 browser이지만 node.js에서 사용한다면 주의사항이 있다.
⚠️ mswjs의 service worker는 브라우저에서만 사용이 가능하기 때문에 node.js에서 사용하려면 별도의 모듈 확장을 통해 가능하다.
MSWJS를 사용함으로써 백엔드 서버 없이도 쉽게 API 호출 시뮬레이션, 테스트, 프로토타이핑, 디버깅 등에 유용하게 사용할 수 있다.
mswjs는 브라우저에 Service Worker(서비스 워커)를 등록함으로써 외부로 나가는 네트워크 요청을 감지한다.
<mswjs 작동 영상 from mswjs docs>
짧은 영상이지만 필요한 개념인 request interception & mock response & service worker 에 관해 나온다.
<mswjs 네트워크 흐름>

mswjs는 network level에 service worker를 두고 요청을 intercept(가로채기)하며 요청에 대한 mock response(모조 응답)을 내려준다.
request를 가로채며(stub; interception) mock response를 내려주는 역할을 담당하며 caching 또한 제공한다.요청을 중간에 service worker가 가로채 msw server에게 전달한다.이 모조 응답을 통해 테스트 및 디버깅을 할 수 있다.msw는 browser에 service worker를 두고 외부로 나가는 request를 intercept한다고 위에서 언급했다. 이 때 intercept(가로채기)를 하는 주체가 resolver(Response Resolver)이다.
요청에 대한 모조 응답을 보내기 위해서는 요청을 먼저 가로채야 한다. request handler 호출을 통해 Service Worker가 요청을 가로챌 수 있게 만들 수 있다.
💡 docs에서는 path를
predicate: (string | RegExp)request handler를resolve: (Resonponse resolver)라고 하는데 편의상path, request handler라고 하자
request handler는 어떤 요청을 가로채고 어떻게 처리할지 대한 함수이다.
간단한 예시를 봐보자.
import { http } from 'msw
http.get('/pets', ({ request, params, cookies }) => {
return HttpResponse.json(['Tom', 'Jerry', 'Spike'])
})
'/pets'라는 route로 요청이 오면 모조 응답으로 Tom, Jerry, Spike를 요소로 하는 배열을 응답으로 내려준다.
node.js로 서버를 구축하는 것과 매우 비슷하다. mswjs또한 path에 대한 여러가지 기능을 제공한다.
string pathname: 정확히 string과 일치하는 path에 대한 request를 가로챈다.
http.get('/pets', request handler)
wild card: 부모 path(/pets)가 같은 보든 path에 대한 request를 가로챈다.
http.get('/pets/*', requeste handler)
RegExp: 정규식을 통해서 가로채질 request를 정의할 수 있다.
http.delete(/\/settings\/(sessions|messages)/, resolver)
MSW는 WHATWG API 명세를 준수하여 모든 mock response(모조 응답)은 WHATWG API 명세서의 내용과 동일하다.
가로챈 request에 대해서 mock response를 해주는 방법은 유효한 response 인스턴스를 return함으로써 수행할 수 있다.
import { http } from 'msw';
export const handler = [
http.get('/resource', () => {
return new Response("Hello MSW");
}
]
위와 같이 Response를 이용하여 응답하는 방법도 있지만, MSW에서는 아래 두 가지 이유에 근거하여 자체 HttpResponse를 import하여 사용하길 권장하고 있다.
- HttpResponse 클래스는
json, xml, formData 등과 같은 단축 메서드를 캡슐화한다.- browser에서 제공하는
Response와 다르게HttpResponse를 이용하면Set-Cookie 등을 설정하여 mock response에 쿠키를 담을 수 있다.
위의 2가지의 기능을 편하게 사용하고 싶다면 MSW에서 제공하는 HttpResponse 객체를 사용하면 좋을 듯 하다. docs에서는 결국 HttpResponse는 Response instance를 return하며 native response와 100% 호환이 된다고 한다.
response에 상태코드를 담아 보낼 수 있듯이 mswjs도 가능하다.
import { http, HttpResponse } from 'msw';
export const handlers = [
http.get('/apples', () => {
return new HttpResponse(null, {
status: 404,
statusText: 'Out of Apples'
})
})
]
원래는 native Response객체에 headers에 지정할 수 없는 것들은 msw에서 제공하는 HttpResponse를 통해서 지정할 수 있다.
import { http, HttpResponse } from 'msw'
export const handlers = [
http.post('/auth', () => {
return new HttpResponse(null, {
headers: {
'Set-Cookie': 'mySecret=abc-123', // 👉🏻 cookie 설정
'X-Custom-Header': 'yes',
},
})
}),
]
HttpResponse도 native Response와 마찬가지로 Body에 plainText 및 json 등등을 작성하여 응답할 수 있다.
import { http, HttpResponse } from 'msw'
export const handlers = [
http.get('/name', () => {
// HttpResponse('John')를 'text' shorthand method를 이용하여 작성할 수 있다.
// HttpResponse.text('John');
return new HttpResponse('John')
}),
]
이 외에도 error를 전달할 수도 있다.
// error 전달
import { http, HttpResponse } from 'msw'
export const handlers = [
http.post('/checkout/cart', () => {
return HttpResponse.error()
}),
]
// throw error
import { http, HttpResponse } from 'msw'
http.post('/login', ({ request }) => {
if (!request.headers.has('cookie')) {
throw new HttpResponse(null, { status: 400 })
}
})
⚠️ error를 throw하면 4xx, 5xx에 대해 response.ok를 자동으로
false로 설정된다.
get, post이 외의 request method인 patch, put, delete도 native response와 100%호환이므로 사용이 가능하다.
mswjs를 사용하기 위해 library를 설치하자
yarn add -D msw
pulblic directory에 mockServiceWorker.js란 파일이름으로 둠으로써 service worker를 호스팅할 수 있다.
$) npx msw init <PUBLIC_DIR>
$) npx msw init ./public
명령어를 실행하면 public directory하위에 mockServiceWorker.js가 만들어지며 domain/mockServiceWorker.js로 routing하면 service worker에 대한 script를 볼 수 있다.
이제 service worker를 등록하자
// src/mocks/browser.ts
import { setupWorker } from 'msw/browser'
import { handlers } from './handlers'
export const worker = setupWorker(...handlers)
msw에서 service worker를 실행시키기 위해선 worker.start()를 통해 실행시킬 수 있다. service worker는 비동기 연산을 수행한다.
// main.tsx
import App from './App';
// * run the mocking api before createRoot called
async function enableMocking() {
// process.env.NODE_ENV
if (import.meta.env.MODE !== 'development') {
return;
}
const { worker } = await import('@/mocks/browser');
return worker.start();
}
// service worker가 실행된 후 APP이 mount됨
enableMocking().then(() => {
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
);
});
mocking이 완료되면 browser의 console창에서 아래와 같이 확인할 수 있다.

[MSW] Mocking enabled이 뜬다면 정상적으로 msw를 실행시킨 것이다.
💡 vite에서는 process.env.NODE_ENV를 import.meta.MODE로 자체적으로 변환하여 처리한다.
그 전에 mock data가 필요하므로 src/db/userDB.json을 만든다. 이후 request handler를 src/mock/handlers.ts에 정의하여 mock response를 처리한다.
import { HttpResponse, http } from 'msw';
import { userEndpoints } from '@/constants/apiEndpoints';
import data from '@/db/userDB.json';
type UserParams = {
userId: string;
};
const userHandler = [
http.get<UserParams>(userEndpoints.user, async ({ params }) => {
const { userId } = params;
const foundUser = data.user.find(user => user.id === userId);
if (!foundUser)
return new HttpResponse(null, {
status: 404, // 4xx, 5xx에 대해서는 client에서 자동으로 ok=false가 된다.
statusText: `Couldn't found user`,
});
return new HttpResponse(JSON.stringify(foundUser), {
status: 200,
statusText: 'found user!',
headers: {
'Content-Type': 'application/json',
},
});
}),
];
export const handlers = [...userHandler];
user endpoint대한 mock response처리를 request handler에서 처리해주었다. 이를 성공 및 실패시 어떤 msw는 우리에게 어떤 정보를 주는지 확인해보자.
<✅ 성공 시>

<❌ 실패 시>

성공과 실패 시 request handler에서 정의했던 status code & status text를 내려주는 것을 확인할 수 있다.
msw를 공부하면서 아주 많은 에러에 마주했다. 이유는 1 -> 2로 버전 업데이트가 진행되어 많은 것이 바뀌었기 때문이다. docs나 블로그를 참조하면 http 대신 rest를 사용하는데 rest를 msw가 아예 읽지 못 해 혹시나 해서 버전을 찾아본 결과 rest 대신 http로 바뀌었다.
버전이 업데이트 되면서 크게 바뀐 3가지이다.
rest object to http(rest.get -> http.get): rest에서 http로 바뀌었다.setupWorker, SetupWorkerApi etc... must be imported from msw/browser now: browser와 관련된 기능은 msw -> msw/browswer로 분리res, req, ctx => res()) but instead({request}) => new Response(): 매개변수 res, req, ctx는 request객체로 통합나머지 change log들은 여기를 참고하자.
지금 현재 진행중인 프로젝트에 막 도입시킨 것으로 초입의 상태이다. 간단한 msw의 작동방식과 사용을 알아보았다. 나는 귀찮아서 json-server만을 사용했었는데 msw는 response의 interface또한 커스텀 가능하며 실제 서버대신 서버를 사용하는 것과 같은 효과를 만들어 테스트, 디버깅 및 실제 서버에 붙이기 전에 임시 mock data를 사용하기 위한 간단한 REST API 작성이 매우 쉬워서 애용할 듯 하다. msw를 공부하면서 오류 상태 코드에 대한 ok의 응답은 자동으로 false가 되는지 몰라 헤맸었다...🥲🥲🥲