관련 포스트 : 부스트캠프 웹・모바일 7기 그룹 프로젝트 2주차 회고
관련 GitHub : https://github.com/boostcampwm-2022/web06-weview
서버에 배포하면 사이드이펙트가 생길수도 있고, 서버가 다운되면 또 그것대로 해결해야 했다.
그래서 서버에 배포하지 않고 로컬에서 테스트를 하려면 어떻게 해야 할까 고민했는데,
Mock by intercepting requests on the network level. Seamlessly reuse the same mock definition for testing, development, and debugging.
msw.js
해당 라이브러리 홈페이지에 들어가면 바로 나오는 소개다.
네트워크 레벨에서 요청을 가로챈다고 한다. 그게 무슨 의미일까?
래퍼런스의 이미지를 보면 이해가 쉬웠다.
MSW를 실행시키면 실제 서버까지 요청이 전달되지 않고, 네트워크 레벨에서 요청이 처리된다는 부분이 이런 의미다.
많은 도움을 받은 문서에 나와있는 내용을 참고하여 우리 프로젝트에 적용했다.
공식문서가 잘 나와있어서 따라하면 쉽게 적용할 수 있다.
npm install msw --save-dev
# or
yarn add msw --dev
우리 프로젝트에서는 위와 같이 사용했다. (mocks 디렉토리는 src 하위 폴더로 생성했다.)
mkdir src/mocks
공식문서의 Integrate 부분을 참고하면 브라우저 방식과 Node.js 방식이 다르다고 한다. 혹시 적용할 때 참고하자.
# browser
npx msw init <PUBLIC_DIR> --save
# VITE public 디렉토리 선택
npx msw init public/ --save
우리는 브라우저에서 필요하므로 브라우저 방식을 선택했고, 위와 같이 public 디렉토리를 선택해서 초기화 할 수 있었다.
VITE를 사용했으므로 root 디렉토리의 public/ 디렉토리를 선택했다.
// main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { API_MODE } from "./constants/env";
import { MODE } from "./constants/mode";
if (MODE.MOCK === API_MODE) {
import("@/mocks/browser")
.then(async ({ worker }) => {
await worker.start({
onUnhandledRequest: "bypass",
});
})
.catch((err) => {
console.error(err);
});
}
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
먼저 위와같이 환경변수의 실행 환경이 MOCK 환경일 때 msw.js를 초기화하도록 설정했다.
// server.ts
import { setupServer } from "msw/node";
import { handlers } from "./handlers";
// This configures a request mocking server with the given request handlers.
export const server = setupServer(...handlers);
// browser.ts
import { setupWorker } from "msw";
import { handlers } from "./handlers";
export const worker = setupWorker(...handlers);
mock/server.ts파일과 mock/browser.ts 파일을 위와 같이 설정해주고,
//
export const handlers = [
...authHandler, // import 해 온 handler 들
...postHandler,
...reviewHandler,
...rankingHandler,
...searchHandler,
...bookmarkHandler,
];
mock/handlers.ts 파일도 위와 같이 필요한 핸들러를 import해서 배열로 내보냈다.
// handlers/searchHandler.ts
import { rest } from "msw";
import { API_SERVER_URL } from "@/constants/env";
import { history, setHistory } from "@/mocks/datasource/mockDataSource";
// Backend API Server URL
const baseUrl = API_SERVER_URL;
export const searchHandler = [
rest.get(`${baseUrl}/search/histories`, (req, res, ctx) => {
return res(ctx.status(200), ctx.delay(500), ctx.json(history));
}),
rest.delete(`${baseUrl}/search/histories/:id`, (req, res, ctx) => {
setHistory(
history.filter((searchHistory) => searchHistory.id !== req.params.id)
);
return res(ctx.status(204), ctx.delay(1000));
}),
];
그리고 핸들러는 위와 같이 사용했다.
API_SERVER_URL
등은 환경변수로 분리해서 받아왔고, history, setHistory
등은 Mock DB 역할을 수행하는 오브젝트이다. (검색 기록을 간단하게 보관하기 위해서 배열로 사용했다.)
중요한 부분은 rest.get
, rest.delete
이 부분이다.
래퍼런스를 참고하면 더 다양한 기능을 사용할 수 있다.
- 기존 개발 방식에서 FE-BE 개발 속도 차이와 클라이언트단의 API를 사용한 테스트의 불편함이 있었다.
- msw.js 라이브러리를 도입해서 로컬 환경에서 클라이언트가 API를 사용한 기능 테스트가 가능하도록 구현할 수 있게 되었다.
- 또한 환경 변수를 설정해서 개발 환경과 배포 환경에서 소스코드 변경 없는 서버 API와 Mock API를 전환할 수 있었다.
지금 생각하면 2주차에 개발을 거의 시작하자마자 도입하자고 요청했던 것이 굉장히 좋은 선택이라고 생각한다.
지금 우리가 서비스하는 기능들에 대해서 거의 대부분 로컬에서 실행할 수 있을 정도로 설정되어 있어서, 나중에 서버를 닫는다면 정적 배포로 클라이언트만 배포해도 좋을 것 같다는 생각도 들었다.
그리고 더 효율적인 협업을 한다는 느낌이 들었다. 특히 CI/CD 과정에서 자동으로 주입하는 환경변수로 실제 소스코드는 변경 없이 배포하면 자동으로 API서버로 요청이 가는 부분이 굉장히 좋은 개발 경험이였다.
앞으로도 혼자 FE를 개발하거나 BE개발자와 협업한다면 적극적으로 MSW를 도입할 것 같다.