저희 프론트엔드팀은 더 나은 모킹을 위해 MSW를 사용하고 있습니다.
MSW를 적용하면서 그 과정이 생각보다 쉽지 않았고, 가장 최신 버전인 v2에 관련된 레퍼런스도 별로 존재하지 않았습니다.
그에따라 정말 많은 삽질도 했고, 여러번 공식문서를 보며 완벽까진 아닐지언정, 원하는대로 MSW 세팅을 진행했고, 조금 더 유연한 프론트엔트 테스트코드, 개발 환경이 구축되었습니다.
이번 포스팅에는 주니어 개발팀의 고민이 무엇이였는지, 그 고민을 해결하기 위해 어떻게 MSW를 도입하였고 어떤식으로 사용하고 있는지에 대해 공유해보고자 합니다.

프론트엔드팀의 고민
1. 이상적인 개발에 대한 고민
서비스에 하나의 기능이 추가되기까지 정말 다양한 루트가 있습니다. 이상적으로 프론트엔드는 백엔드의 개발이 완료되어 실제적인 API 동작까지 테스트하면서 개발을 한다면 정말 좋겠지만 현실적으론 쉽지 않습니다.
그렇기때문에 프론트엔드는 개발을 완료했어도, 백엔드의 API가 정상적인 기능동작을 테스트하기 전까진 작업을 완료할 수 없고, 이러한 내용은 백엔드 개발자분들에게도 압박으로 다가오게 됩니다.
같이 고생하고 있는 개발자끼리 압박을 주는것이 부담스러웠고, 어떻게 하면 백엔드의 의존성을 좀 덜고 개발할 수 있을까? 라는 고민을 하였습니다.
2. 중복된 모킹 테스트코드
if('로그인 이후 유저 정보를 제대로 받아왔는지 테스트', () => { // 유저 정보 api 모킹 authAPI.getInfo = jest.fn().mockReturnValie({ name: 'User', age: 26 }) ... }) if('유저 정보 변경 테스트', () => { // 유저 정보 api 모킹 authAPI.getInfo = jest.fn().mockReturnValie({ name: 'User', age: 26 }) ... })
authAPI.getInfo
와 같은 유저의 정보를 불러오는 API 가 있다고 가정할때, 이 API를 사용하는 곳은 정말 다양할 것입니다. 같은 데이터를 return 해준다고 해도, 여러번 사용하는 곳에서 Mockup한다면 불필요한 중복코드가 발생할 것입니다.이러한 경우 이외에도 부모 컴포넌트의 API를 가공해서 자식 컴포넌트에서 새로운 API를 받아온다면, 테스트코드에서 부모부터 하위까지 전부 Mockup 처리를 진행해야 할 것입니다.
유연한 테스트코드를 작성하기에 과도한 API모킹과 중복코드는 불필요하다 판단했고, 어떻게하면 이를 해결할 수 있을까? 라는 고민을 하였습니다.
3. 개발환경의 API 테스트
1번의 ‘이상적인 개발에 대한 고민’ 과 유사하지만 조금 더 디테일을 넣자면 대시보드와 같은 데이터 시각화의 다양한 형태를 미리 볼 수 있으면 좋겠다 라는 고민을 했습니다.
예를들어 매출과 관련된 대시보드를 구상한다고 할때 다양한 형태의 대시보드를 확인하고자 임의로 개발환경의 매출 데이터를 변경하게 된다면 개발자의 불필요한 리소스가 생기게 될 것이고 서로의 연관성을 갖고있는 DB의 데이터들이 문제를 발생할수도 있게될 것입니다.
하지만 굳이 연관된 다른 데이터들의 관심을 분리시켜 대시보드만 난수와 같은 여러 형태의 값으로 제공해준다면 조금 더 다양한 형태의 대시보드를 미리 볼 수 있지 않을까? 라는 생각을 하게되었습니다.
MSW 를 선택한 이유
MSW를 간단하게 살펴보자면 MSW의 풀 네임은
Mock Service Worker
로 간단하게 API Mockup 라이브러리
입니다. 공식문서에서는 ‘테스트 경계 설정’, ‘응용 프로그램 프로토타입’, ‘네트워크 관련 문제 디버깅’, ‘프로덕션 트래픽 모니터링’ 등의 기능을 제공한다고 합니다.‘테스트 경계 설정’은 2번의 고민, ‘응용 프로그램 프로토타입’, ‘네트워크 관련 문제 디버깅’은 1번, 3번의 문제를 해소할 수 있을것이라 기대했고, 복잡한 로직이 필요없고, 서비스워커를 이용해 네트워크를 가로채는 작업을 보다 간단하게 처리할 수 있습으며, 별다른 로직 처리없이 브라우저(browser)와 테스트 환경(node)를 구분할 수 있다는 장점이 마음에 들었습니다.
그리고 무엇보다 node.js로 API를 개발하는것과 정말 유사하기에 별다른 학습이 필요하지 않는다는 것에서 MSW를 선택하지 않을 이유가 없을 것 같다는 생각을 하여 MSW를 적용하게 되었습니다.
MSW 적용 방법
우선 기존의 v1에 익숙하시고, v2로 옮길 생각이 있으시다면 [msw 1.xx to 2.xx] 참고해주세요. 이하 설명은 v2에 관한 내용으로 생략된 부분이 있을 수 있습니다.
MSW 설치
// npm npm install msw --save-dev // yarn yarn add msw --dev
Browser Service Worker 생성
npx msw init public/ --save
위 설정은 Browser 환경에서 API를 가로채기 위해 사용되는 파일입니다. Jest(node)환경에서만 사용할 예정이라면 위 설정을 건너 뛰어도 상관 없습니다.
Service Worker 설정하기
// browser.ts (브라우저 설정) import { setupWorker } from 'msw/browser'; import { handlers } from './handlers'; export const server = setupWorker(...handlers); // node.ts (server 설정) import { setupServer } from 'msw/node'; import { handlers } from './handlers'; export const server = setupServer(...handlers);
위 설정도 Jest 환경에서만 사용할 경우에는 browser.ts 파일이 필요하지 않습니다.
Handler 설정하기
우리가 원하는 API 경로로 요청을 보낼때 우리가 설정한 Mockup Data를 Return 해주기 위해서는 API경로와 Mockup Data를 맵핑하고 정의하는 부분을 작업해야합니다.
위 작업을 하는 곳이 Hanlder 인데, 작업하는 내용을 보면 node.js 의 API 형태와 매우 비슷하다고 생각할 수 있습니다. 같은 JS 코드라 더 유사함을 느끼는 것 같은데요, 그렇기때문에 Mockup 작업에 러닝커브가 매우 적다는 장점이 있습니다. (이렇게 풀스택으로..)
// handler.ts import { http } from 'msw'; import { getNewUserStatistics } from '@src/server/controllers/user/user'; export function sleep(ms: number): Promise<unknown> { // 300ms 이내의 API 는 suspense 의 영향을 받지 않음. return new Promise((r) => { setTimeout(r, ms); }); } export type ServerParams<T extends (...args: any) => any> = Parameters<T>[1]; const getDashboardData: ServerParams<typeof http.get> = async () => { await sleep(300); return HttpResponse.json( { dashboardData: [//데이터], }, { status: 200 }, ); }; const dashboard = [http.get('/api/dashboard', getDashboardData)]; export const handlers = [...dashboard];
이렇게 API가 늘어날때마다 Mockup을 하고, handlers에 넣어주기만 하면 API 콜을 할때마다 우리가 원하는 데이터를 받을 수 있습니다. 이는 위에서 브라우저와 노드 환경 둘 다 설정해두었기때문에 동일한 Mockup을 받아볼 수 있습니다.
server 사용하기
// browser server 실행 // index.tsx // 특정 환경에서만 사용 import { server } from '@src/server/browser'; if (process.env.REACT_APP_ENV === 'msw') { server.start(); } // node server 실행 // jest.setup.ts import { server } from "./src/server/node"; beforeAll(() => { server.listen({onUnhandledRequest: 'warn'}); }); afterEach(() => server.resetHandlers()) afterAll(() => { jest.clearAllMocks(); server.close(); });
browser와 node의 Mockup server의 실행은 각각 다르기 때문에 환경에 맞게 실행을 진행해야 합니다.
- 브라우저에서 Mockup data를 사용하려면 ‘browser’의 server를 start 해주어야 합니다.
- 노드에서 Mockup data를 사용하려면 ‘node’의 server를 listen 해주어야 합니다.
MSW 세팅 중 생긴 이슈
혹시나 v1에서 v2로 업그레이드나, v2로 환경을 세팅하려는 분들이 계실 수 있기 때문에 세팅하면서 생긴 이슈가 있어서 간단하게 공유하고자 합니다.
ReferenceError
ReferenceError는 총 3가지가 있습니다.
ReferenceError: TextEncoder is not defined ReferenceError: TextDecoder is not defined ReferenceError: ReadableStream is not defined
위 문제는 해당 이슈에서도 나온 내용이고, 공식문서에서도 해결방안이 있습니다. node 환경에서는
ReadableStream
에러가 발생하지 않을 것입니다. 간단하게 jest.polyfills.js 파일을 추가해서 해결할 수 있습니다.// jest.polyfills.js const { TextDecoder, TextEncoder } = require('node:util') const { ReadableStream } = require('node:stream/web') if (globalThis.ReadableStream === undefined) { globalThis.ReadableStream = ReadableStream } Object.defineProperties(globalThis, { TextDecoder: { value: TextDecoder }, TextEncoder: { value: TextEncoder }, }) const { Blob, File } = require('node:buffer') const { fetch, Headers, FormData, Request, Response } = require('undici') Object.defineProperties(globalThis, { fetch: { value: fetch, writable: true }, Blob: { value: Blob }, File: { value: File }, Headers: { value: Headers }, FormData: { value: FormData }, Request: { value: Request }, Response: { value: Response }, })
이렇게 추가한 polyfills 파일을
jest.config.js
에 추가합니다.module.exports = { ... // 다른 옵션들 생략 setupFiles: ['./jest.polyfills.js'], setupFilesAfterEnv: ['./jest.setup.tsx'], };
MSW로 고민 해결
1. 이상적인 개발과 개발환경의 API 테스트
우리는 MSW 를 활용해 이상적인 개발이 가능해졌습니다. msw browser 환경을 설정해두었고, Feature의 API를 Mockup data로 대체하여 백엔드와의 병렬작업이 가능한 구조로 변경되었습니다.
또한 다양한 차트를 제공하는 대시보드의 경우 난수로 설정된 Mockup data로 다양한 모습의 대시보드를 테스트할 수 있게 되었습니다.

개발서버의경우 실제 데이터가 누적되지 않아서 대시보드를 보기 어려운 부분이 있었는데, Mockup data를 활용하면서 좀 더 유연한 개발에 집중할 수 있게 되었습니다.
2. 중복된 모킹 테스트코드
authAPI.getInfo
와 같은 유저의 정보를 불러오는 API를 MSW를 통해 Mockup data 설정을 해두었다면, get을 Mockup하는 부분이 전부 빠져도 문제가 없게 되었습니다. server.listen
을 통해서 테스트때의 Mockup 설정해둔 API를 불러오고, 가공하여 테스트를 할 수 있게 되었습니다. 이로써 우리는 중복된 테스트 코드를 신경쓰지 않아도 되고 조금 더 로직에 집중할 수 있게 되었습니다.이렇게 설정까지 정말 다양한 시행착오가 있었지만 정상동작하는 것을 보니 마음이 좋네요..
하지만 매번 개발할때 Mockup 설정을 하는 단계가 생겨 개발이 늦어질 수도 있습니다. 적당한 분배를 통해 너무 많은 시간을 Mockup 설정하는곳에 쓰지 않고 개발하는 방법을 더 공부해야 할 것 같습니다.