프론트엔드 개발은 프로젝트 완성의 마지막 단계입니다. 디자인이 완료되면 UI를 작업하고, UI 완료 시점에 API가 완성이 되어있다면 더할 나위 없이 좋겠지만 실상은 그렇지 못하게 흘러가는 경우가 많습니다.
현재 사이드 프로젝트를 진행하면서 해당 이슈가 생겨 백엔드의 의존도를 줄여 개발 속도를 향상시키기 위해 MSW를 도입해 사용해 격차를 줄이기 위한 노력했습니다!
Mocking이란?
웹 개발 과정에서 백엔드 시스템이나 외부 API와의 통신이 필요한 경우가 많습니다.
이때, 실제 서비스나 API가 준비되지 않았거나, 테스트를 위해 특정 응답을 조작해야 할 필요가 있을 때 모킹이 필요합니다.
Mocking을 통해 개발 초기 단계에서부터 백엔드와의 통신을 가정하고 프론트엔드 개발을 진행할 수 있게 해주며, 단위 테스트에서도 외부 의존성 없이 코드를 검증할 수 있게 해줍니다.
Mocking 방법으로는 MSW, JSON Server, Cypress Intercept, SuperTest 등이 있습니다.
필요한 상황에 맞게 선택해 활용하면 됩니다! 그중 저는 MSW를 선택해 개발했습니다.
MSW란 무엇인가?
MSW 공식문서 링크는 아래와 같습니다.
Mock Service Worker로 브라우저와 Node.js 환경에서 네트워크 요청을 가로채고 모킹 할 수 있는 라이브러리입니다.
MSW는 Service Worker API를 사용해 실제 네트워크를 가로채고, 개발자가 정의한 응답으로 대체하기 때문입니다.
이를 통해 실제 백엔드 서버 없이도 API 요청과 응답을 시뮬레이션할 수 있습니다.
또한 지연 응답, 네트워크 오류, 상태 코드 변화 등 다양한 네트워크 상황을 모킹 할 수 있는 기능을 제공합니다.
이를 통해 사용자가 겪을 수 있는 다양한 네트워크 환경을 테스트하고, 애플리케이션의 견고성을 높일 수 있습니다.
MSW는 REST API 뿐만 아니라 GraphQL API 모킹도 지원합니다.
MSW는 설정이 간단하고, 실제 서버와 유사한 환경을 제공하기 때문에 개발자 사이에서 인기가 높다고 합니다.
Service Worker란?
웹 애플리케이션의 메인 스레드와 분리된 별도의 백그라운드 스레드에서 실행시킬 수 있는 기술 중 하나입니다.
Service Worker 덕분에 애플리케이션의 UI Block 없이 연산을 처리할 수 있습니다.
Service Worker는 네트워크가 원활할 때 동기화를 시켜주는 백그라운드 동기화 기능, 높은 비용의 계산을 처리할 때, 푸시 이벤트를 생성할 때 주로 사용됩니다.
그 외에도 Service Worker가 애플리케이션과 서버 사이에서 Request를 가로채 직접 Fetch에 대한 컨트롤도 할 수 있기 때문에 HTTP Request와 Response를 보고 캐싱처리, 로깅 등등을 할 수 있습니다.
Service Worker는 localhost가 아닌 환경에서는 HTTPS가 기본적으로 제공되는 환경에서만 사용할 수 있습니다.
MSW를 왜? 언제 사용하는가?
현대 웹 개발에서 개발 초기 단계에서 백엔드 서비스가 준비되지 않았을 때, 또는 통합 테스트를 수행할 때 중요한 역할을 합니다.
API 모킹을 통해 실제 백엔드 서비스 없이도 프론트엔드 애플리케이션의 동작을 시뮬레이션 할 수 있기 때문입니다.
개발 속도를 높이고, 백엔드와의 통합 부분에서 발생할 수 있는 문제를 사전에 발견하고 해결하는데 도움을 줍니다.
실제 현업에서도 기획 -> 디자인 -> 개발 순인데, 개발을 진행할 때 백엔드 API 개발이 예상보다 늦어져 발생하는 갭을 줄이고자 할 때 많이 사용됩니다.
왜 MSW를 선택했는가?
나는 왜 MSW를 선택했는가? 서버 개발자와 협업을 하지만 서로 할애하는 시간이 달라 FE의 작업 속도와 서버 API 개발 속도의 차이가 많이 나기 시작해 UI 이상의 작업이 어려워졌기 때문입니다.
이전에는 Mock Data를 하드코딩해 불러와 뿌려주는 방향을 사용했지만, 실제 백엔드 API와 통신하는 것처럼 react-query에 요청 형태를 동일하게 작업할 수 있다는 점이 장점으로 다가왔습니다.
이후 테스트를 진행하는 과정에도 MSW를 통해 원하는 데이터를 전달해 수월한 테스트 환경도 경험해보고 싶었습니다.
장점과 단점은 무엇인가?
장점은 위에 선택 이유와 제공해 주는 기능들입니다.
실제 API 없이 빠르게 개발할 수 있고, 실제 네트워크 환경과 유사하게 동작하기 때문에 CORS, 네트워크 지연과 실패 등 시나리오를 쉽게 모킹 할 수 있습니다.
API 모킹 설정을 파일로 분리해 관리할 수 있어, 복잡한 API를 체계적으로 관리할 수 있습니다.
테스트 도구와 쉽게 통합이 가능하고, 일관된 모의 응답을 사용해 안정적인 테스트가 가능합니다.
제가 선택한 이유였던 백엔드와의 의존성을 줄이고 프론트엔드 주도 개발이 가능해집니다.
그리고 외부 API를 호출하지 않기 때문에 실제 데이터에 영향을 미치지 않는다는 것도 장점입니다.
단점은 API 응답을 사실 결국 수동으로 정의해야 하기 때문에 실제 백엔드와 불일치가 생길 수 있습니다.
(실제로 저도 미리 필요한 데이터를 공유해 진행했지만 백엔드 개발 과정에서 응답 형태가 변경되는 경우가 종종 있더라고요!)
그 외로 초기 설정과 학습 곡선, 백엔드와의 통합 테스트 부족, 네트워크 요청 추적이 어렵다는 단점이 있습니다.
그래서 MSW를 사용하는 게 항상 좋다는 아닙니다!
백엔드가 미완성인 초기 개발 단계이고, API의 복잡도가 크지 않아 명확한 모킹 데이터로 작업할 수 있을 때
독립적인 프론트엔드 개발 및 테스트가 필요한 상황이거나 API 호출 비용이 크거나 제한이 있을 때 사용하면 좋습니다.
MSW 사용방법
1. msw 설치
$ npm install msw --save-dev
$ yarn add msw --dev
2. msw 세팅
$ npx msw init <PUBLIC_DIR> --save
# next.js일 경우
$ npx msw init public/ --save
- public 폴더에 mockServiceWorker.js가 생성된다.
3. request Handler 정의
src에 mocks 폴더를 생성 후 handlers.js 파일을 생성합니다.
request Handler에 (ex. http.get(), graphql.query())를 사용해 네트워크를 설명합니다.
request Handler는 request를 가로채고 response를 처리하는 역할을 합니다.
response는 mock, 실제 response와 mock의 조합, 또는 트래픽에 영향을 미치지 않고 모니터링만 하려는 경우 아무것도 하지 않을 수 있습니다.
import { http, HttpResponse } from 'msw';
import { MainData } from './data';
export const handlers = [
http.get('/main', () => {
return HttpResponse.json(MainData);
})
];
저는 별도의 JSON 파일을 만들어 불러오는 형태로 작업해 mocks 폴더 내 data.js 에 정의해 불러와 사용하는 형태입니다.
단순 테스트를 위해서는 아래와 같이 적용해 볼 수 있습니다.
import { rest } from 'msw'
const handler = [
rest.get('/api/products', (req, res, cts) => {
return res(
ctx.status(200),
ctx.delay(4000),
ctx.json({
"items": [
{ "name": "product-1" },
{ "name": "product-2" },
]
})
)
})
]
export default handlers
src에 mocks 폴더 내 browser.js 파일을 생성합니다.
import { setupWorker } from 'msw'
import { handlers } from './handlers'
export const worker = setupWorker(...handlers)
msw를 실행시키기 위한 코드를 추가합니다.
// src/index.tsx
if (process.env.NODE_ENV === 'development') {
const { worker } = require('./mocks/browser')
worker.start()
}
ReactDOM.render(<App />, document.getElementById('root'));
'use client';
import { useState, useRef, useEffect } from 'react';
export function MockProvider({ children }: { children: React.ReactNode }) {
const [isMocking, setIsMocking] = useState(false);
const isWorkerStarted = useRef(false);
useEffect(() => {
async function enableApiMocking() {
if (typeof window !== 'undefined' && !isWorkerStarted.current) {
isWorkerStarted.current = true;
const { worker } = await import('../../mocks/browser');
await worker.start({ onUnhandledRequest: 'bypass' });
setIsMocking(true);
}
}
enableApiMocking();
}, []);
if (!isMocking) {
return null;
}
return <>{children}</>;
}
export default MockProvider;
3. React에서 MSW 활용하기
MSW는 개발 모드에서만 활성화되도록 설정하는 것이 일반적입니다.
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import { worker } from './mocks/browser'
if (process.env.NODE_ENV === 'development') {
worker.start()
}
ReactDOM.render(<App />, document.getElementById('root'))
적용했던 PR https://github.com/f-lab-edu/eqcm/pull/21/commits/848ee551a3096617af78917ad620dc19cddbbf52
(참고로 해당 글을 작성하면서 위 worker.js는 불필요하다는 것을 알게 되었습니다. 이런 게 바로 블로그로 정리하며 얻는 장점!)
참고한 대표적인 자료들은 아래와 같습니다!
댓글