MSW로 api mocking 하고 프론트엔드 DX 광명찾기

GY·2022년 11월 8일
0

Basic CS

목록 보기
28/28

💡 프론트엔드 개발 과정에서의 API 개발 작업에 대한 의존성을 줄이고
DX(Developer eXperience)를 개선하기 위해 msw를 도입한 내용에 대해 정리합니다.

프론트엔드 개발을 할 때는 api 작업 완료 전까지 mock data를 만들어 작업을 하곤 하는데, 이 때 DX를 저하시키는 요인들이 있습니다.


Mock data 하드코딩의 단점

  • 하드코딩한 mock data는 이후에 코드를 제거하고 실제로 api를 연동하는 코드를 넣어주는 작업을 해야합니다.
  • API 호출을 통해 얻어오는 응답값과 동작흐름이 달라 교체해야 할 코드의 양이 많아질 수 있습니다.
  • api 호출을 위한 함수는 빈 채로 두고, 비즈니스 로직에 mock data를 이용한 코드가 들어가면 불필요한 코드를 꼼꼼히 삭제하거나 교체하지 못하는 경우가 생깁니다.

개발을 진행하던 에셋 라이브러리는

  • 한 가지 엔드포인트로도 여러 쿼리파라미터를 사용해 타입, 장르, 공개여부 등등 다양한 조건의 에셋을 불러와 사용해야 했고, 비즈니스 로직과 엮어 이를 위한 mocking을 하는 건 비효율적이었습니다. (애매…)
  • 신규 등록할 라이브러리 에셋 테스트, 중간 데모, 튜토리얼 영상에 대비해 전체적인 그림을 그릴 수 있도록 작업하는 것이 필요했습니다.

MSW (Mock Service Worker)

  • 브라우저의 Service Worker에서 동작합니다.
💡 Service Worker? - 웹 어플리케이션의 비즈니스 로직과 별개로 브라우저 단에서 별도의 작업을 실행시킬 수 있는 곳

1) 앱의 비즈니스 로직 내에서 mock data를 사용하면, 실제 api 연동 코드로 교체하는 작업 리소스가 추가로 듭니다.

2) MSW를 사용해 앱 내부가 아닌 브라우저 내부의 별도 Service Worker에서 mocking을 합니다.

3) 별도의 웹서버를 만들면 비즈니스 로직에 mock data가 들어가진 않지만 웹 서버를 만들어야 하는 부담이 있습니다.


MSW 작동 방식

브라우저에서 이루어지는 실제 네트워크 요청들을 Service Worker가 가로채게 됩니다. Service Worker는 가로챈 요청을 복사해서 실제 서버가 아닌 클라이언트 사이드에 있는 MSW 라이브러리로 보낸 후, 등록된 핸들러를 통해 가져온 mocking된 응답값을 브라우저에게 그대로 전달해 줍니다.

이러한 과정을 통해, 실제 서버와 직접적인 연결 없이 보내는 요청에 대한 응답을 Mocking 할 수 있게 되는 것이죠. 따라서 백엔드 API가 아직 준비되지 않아도 MSW로 가상 API를 등록하고 프론트에서 테스트할 수 있습니다.

별도의 api 서버가 아닌 MSW를 사용하는 이유

별도의 Mock API Server를 사용하기 위해서는 서버 개발에 대한 지식을 바탕으로 웹서버를 만들고 ⇒ 그 서버를 별도로 함께 로컬 실행해야 합니다.

즉, 최소한의 개발 리소스로 api 연결까지 하고 싶은데.. 가장 효율적인 방법은 아닐지도? 🤔


MSW의 장점

정리하면, MSW로 mocking해 데이터를 사용했을 때의 장점은 다음과 같습니다.

  1. 비즈니스 로직에 mock data관련 코드가 들어가지 않습니다.
  2. 세팅하는데 소스가 적게 들고 쉽게 사용할 수 있습니다.
  3. 실제 API를 호출해 응답값을 받아오듯이 클라이언트에서 사용할 수 있어, 실제 api 엔드포인트로 교체해주기만 하면 별도의 코드 교체 작업이 크게 필요하지 않습니다.

이렇게 사용할 수 있어요

mocks/ 폴더 하위의 worker.js와 최상위 index.js에 세팅 코드가 있습니다.

// mocks/worker.js

import {setupWorker} from "msw";
import {handlers} from "./handlers";

export const worker = setupWorker(...handlers);
// src/index.js

if (process.env.NODE_ENV === "development") {
  worker.start({
    quiet: true,
    onUnhandledRequest: "bypass",
  // 실제 api request까지 가로채기 때문에 
  // 해당 api 요청이 handler에 등록되지 않은 경우 많은 warning 로그가 콘솔에 쌓입니다.
  // 다른 개발 과정에 방해가 될 수 있어 이 로그를 출력하지 않고, unHandledRequest를 무시하는 설정입니다.
  // 로컬 테스트 시 삭제 혹은 주석 처리해 로그를 확인할 수 있습니다.
});
}

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>

mocks/ 폴더 하위 파일에 사용할 mock data를 정의합니다.

// mocks/asset.js

export const mockData = [
  {
    id: "id",
    assetType: "assetType",
    url: "url",
    genre: "genre",
//...
//mocks/handlers.js

// data를 요청에 따라 어떻게 리턴할 것인지 handler를 작성합니다.

import {rest} from "msw";
import {mockData} from "./asset";

export const handlers = [
  rest.get("/path", (req, res, ctx) => {
    const type = req.url.searchParams.get("type");
    return res(
      ctx.status(200),
      ctx.json(
        mockData.filter(asset => {
          return asset.type === type;
        }),
      ),
    );
  }),

실제 api를 호출하듯 사용합니다.

// /api

export const getMockData = type => {
  return fetchRequest(`/path`, METHOD.GET);
};
// api를 호출할 component 내부
// 실제 api 연결을 위해서는 엔드포인트만 교체해주면 됩니다.

useEffect(() => {
    handleMockData();
  }, []);

  const handleMockData = async () => {
    const res = await request.getMockData(params);
    const json = await res.json();
    setData(json);
  };

노션에 관련 내용들과 사용방법을 정리해 팀원들에게 공유하고, 설정 및 예시 코드를 PR로 올려 리뷰를 요청한 뒤 적용하게 되었습니다.
프론트엔드에서도 다양한 응답 케이스별 처리 로직을 작성해 테스트할 수 있고, 이에 대한 응답값 형태를 정의해 백엔드와의 작업을 고려하며 보다 효율적으로 작업할 수 있다는 점에서 충분히 고려해 볼 수 있는 선택지라고 생각합니다.


Reference

profile
Why?에서 시작해 How를 찾는 과정을 좋아합니다. 그 고민과 성장의 과정을 꾸준히 기록하고자 합니다.

0개의 댓글