프론트엔드 개발을 하다보면 와이어프레임 기다리고, 디자인 기다리고, 서버 API 개발까지 기다리다보면 난 개발 언제해???? 소리가 나온다.
카카오 FE 개발자 분도 그걸 느꼈는 지 가상의 서버 만드는 기술을 블로그에 적어주셨다. 우연히 그걸 봤는데 나의 고민이 담긴 글과 함께 해결책을 제시해주셨다.
그건 바로 MSW
MSW : Mocking Service Worker
단어는 어려운데 가짜 서버와 API통신을 테스트하는 것이다. 가짜 서버를 목서버(mock server) 라고 하는데 백엔드 API가 나오기 전에 목서버와 API 통신을 하여 나중에 백엔드 API를 붙일 때 fetch 함수만 변경해주면 된다 !!!
그래서 다음 이미지와 같이 API가 나올때까지 기다리는 시간을 줄이고 전체 개발시간도 줄일 수 있다.
npm install --save-dev msw
npx msw init ./public --save
위 명령어를 실행하면 ./public/mockServiceWorker.js
파일이 생성된다.
mocks 디렉토리를 생성하고 browser.js
, handler.js
파일을 생성한다.
// src/mocks/browser.js
import { setupWorker } from 'msw';
import handlers from './handler';
export const worker = setupWorker(...handlers);
// src/mocks/handler.js
const { rest } = require('msw');
const handlers = [
rest.get('/api/product', (req, res, ctx) => {
return res(
ctx.status(200),
ctx.delay(2000),
ctx.json({
result: [{ name: 'Park' }]
})
);
})
];
export default handlers;
entry 파일에 다음과 같은 코드를 작성한다.
next에서는 react와 동작방식이 달라 [MSW] Failed to execute setupWorker in a non-browser environment.
라는 오류가 발생하므로 분기처리를 해줘야 한다.
// _app.tsx
if (process.env.NODE_ENV === 'development') {
(async () => {
const { worker } = await import('../src/mocks/browser');
worker.start();
})();
}
fetch('/api/product').then((res) => console.log(res));
오호라 되게 신기하다 말되나? 이제는 서버없이도 해낼 수 있다 !!!
API 명세가 나오더라도 자주 바뀌는데 목서버를 통해 값이 제대로 넘어오는 지 테스트를 해보고 마지막에 바꿀 수 있어서 좋은 것 같당
잠시 한눈판 사이에 2 버전이 나왔다!
23년 8월에 해커톤 때 적용했던 MSW 버전이 1.2.3 인데 앞자리가 바뀐 2버전이 나온 지금은 24년 1월...5개월만에 나왔다.
오류없이 1.2.3 을 설치하려 했지만 typescript 버전을 5.2.2 로 올려서 5.0.x 까지만 호환되는 1 버전은 사용하지 못한다. 다시 공부해보자.
실제 서버는 내려가있는 상태였고, 프로젝트 구조가 API 종속적
이였다. 상태를 hooks로 관리하는 것이 아니라 Mobx의 Repository, Model 구조에 따라 API로 불러온 데이터를 렌더링한다. 따라서 더미데이터로 동작시키기에 어려움
이 있었고, 데이터가 없을 때는 화면에 아무것도 보이지 않아 컴포넌트 구조를 파악하기 어려웠다.
하지만 실제 서버가 올라올 때까지 기다릴 수 없었다. 그래서 msw를 도입하기로 결정하였다.
라이브러리 적용 및 유지보수가 어려워 전체 패키지 버전업을 진행하였고, 마이그레이션 후 msw 2.0.13 버전을 설치하여 API mocking을 진행하였다.
실제 서버가 구동되더라도 추후에 서버가 내려갔을 때 프론트엔드 코드 유지보수에 도움이 될 거라고 생각하여 끝까지 개발할 예정이다.
위에서 설명한 것처럼 아래 명령어를 실행하여 ./mockServiceWorker.js
생성
npm install --save-dev msw
npx msw init ./public --save
이전과 다르게 browser과 handler를 ts 파일로 설정하였는데 dynamic import 시
const { worker } = await import('./mocks/browser');
문장에서 암시적으로 any 타입을 반환한다고 오류가 발생하여 수정하였다.
browser.ts
import { setupWorker } from 'msw/browser';
import handlers from './handler';
export const worker = setupWorker(...handlers);
handler.ts
1 버전과 다르게 HttpResponse
객체 모듈을 사용해 응답을 반환한다
import { http, HttpResponse } from 'msw';
const handlers = [
http.get('/api/test', () => {
return HttpResponse.json({ id: 'abc', name: 'Gyu' });
}),
];
export default handlers;
index.tsx
async function enableMocking() {
if (process.env.NODE_ENV !== 'development') {
return;
}
const { worker } = await import('./mocks/browser');
return worker.start();
}
enableMocking().then(() => {
render(
<React.StrictMode>
...
</React.StrictMode>,
document.getElementById('root'),
);
});
보통 GET 요청만 mocking해서 컴포넌트 렌더링 되는지 테스트하는 용도로 사용했는데 POST 요청을 mocking 해보자.
mocking을 많이하다보니 한 파일에서 보기 복잡해서 카테고리별로 handler 파일을 나누고, 중복되는 mock data 또한 따로 분리하였다.
mocks/handler.ts
const handlers = [
http.get('/api/v1/posts', getAllPostHandler),
http.post('/api/v1/posts', createPostHandler),
...
];
mocks/postHandler.ts
작성한 게시글의 데이터를 body로 넘겨 DB에 저장한다.
1 버전에서의 request.body 대신 await request.json()
을 통해 request body를 얻을 수 있다.
기존의 mock data인 contentList에 넣으면 GET API 에서도 불러와지는 것을 확인할 수 있다. 대신 새로고침하면 추가한 데이터는 다시 사라진다.
import { contentList } from './mockData';
export const createPostHandler = async ({ request }: { request: Request }) => {
const body: Post.CreateRequestDto = await request.json();
contentList.push({
title: body.title,
content: body.content,
...
});
return HttpResponse.json(...)
};
https://tech.kakao.com/2021/09/29/mocking-fe/
https://mswjs.io/docs/migrations/1.x-to-2.x/
https://velog.io/@hamsoo159/React-MSW-2.0%EC%9D%84-%EC%95%84%EC%8B%9C%EB%82%98%EC%9A%94