msw Mocking데이터 만들기

이재진·2023년 12월 13일
0

모애프로젝트

목록 보기
12/16

모킹데이터를 만들어서 api요청을 미리 만들어서 기능 구현해보기

이전 상황

프로젝트를 새롭게 들어가면서 백엔드가 작업을 들어갔고, 디자인팀에서도 디자인을 하는 중이라, 프론트엔드는 손가락 쭙쭙 빨고 있는 상황이었다.
그래서 뭐라도 해보자 해서
dummy.json형태의 파일을 만들어서 next.js에서 제공하는 require을 활용해서 기능구현을 하고 있었다. 하지만 이건 어디까지나 더미를 불러오는 역할일 뿐이라서 실제로 api요청을 통한 기능구현이 불가능했다.


그 와중에 선배님께서 mocking데이터데 대한 의견을 주셔서 한 번 진행해 보기로 했다.

MSW?

MSW(Mock Service Worker)는 브라우저 및 Node.js용 API 모의 라이브러리입니다. MSW를 사용하면 나가는 요청을 가로채서 관찰하고 모의 응답을 사용하여 응답할 수 있습니다.
공식문서

Next.js에서의 MSW

vscode에서 라이브러리를 다운받아서 공부를 하고있는데, 뭔가 이상한 점을 찾았다. 인터넷에서 찾은 reference들이 적용이 잘 안되었다.
이유를 찾아보니 MSW버전이 업데이트가 되어서 그랬던 것이다...

적용

npm install msw --save-dev

먼저 라이브러리를 다운했다.

npx msw init public/ — save

워커를 작동시키기위한 모듈도 다운했다.

./src/mocks폴더를 만들어서 해당 작업을 진행했다.

// ./src/mocks/handle
import { HttpResponse, http } from "msw";

export const handlers = [
  http.get("/mento", () => {
    return HttpResponse.json({
      board: {
        id: 0,
        head: "제목",
        body: "본문",
      },
    });
  }),
];

내가 필요한 데이터를 적용시킬 수 있다.

브라우저 환경을 설정해주고

// ./src/mocks/browser.ts
import { setupWorker } from "msw/browser";
import { handlers } from "./handlers";

export const worker = setupWorker(...handlers);

node환경을 설정해 주었다

// ./src/mocks/server.ts
import { setupServer } from "msw/node";
import { handlers } from "./handlers";

export const server = setupServer(...handlers);

실제로 사용하기 위해서는 serverbrowser를 실행시켜줘야한다.

// ./src/page/_app.tsx
import type { AppProps } from "next/app";

export default function App({ Component, pageProps }: AppProps) {
  useEffect(()=>{
  	if (process.env.NODE_ENV === "development") {
    if (typeof window === "undefined") {
      (async () => {
        const { server } = await import("../mocks/server");
        server.listen();
      })();
    } else {
      (async () => {
        const { worker } = await import("../mocks/browser");
        worker.start();
      })();
    }
  }
  }, []);
  return <Component {...pageProps} />;
}

이렇게 적용시켜 주면 사용 준비는 끝이 났다.

사용

사용하는건 너무 간단했다. 기존에 api요청을 보내는 것과 똑같은 방식으로 진행하면 됐다.

// ./src/pages/index.tsx
import axios from "axios";
import { useEffect, useState } from "react";

export default function Home() {
  const [getData, setData] = useState<any>();

  const getMentoApi = async () => {
    const res = await axios.get("/mento");
    setData(res.data.board);
  };

  useEffect(() => {
    getMentoApi();
  }, []);
  return (
    <>
      {getData && (
        <div>
          <div>{getData.id}</div>
          <div>{getData.head}</div>
          <div>{getData.body}</div>
        </div>
      )}
      <div>11123</div>
    </>
  );
}

에러

이렇게 했는데 에러가 발생했다. 문제는 클라이언트 환경에서 실행되게끔 보장해 줘야 한다.
그래서 현재 _app.tsx파일에서 useEffect로 처리된 부분을 컴포넌트로 따로 빼서, return하는 컴포넌트의 상위에 붙여줄 생각이다.


에러해결

이 부분은 윗 기수 선배님의 도움을 받아서 진행했다. msw2.0 버전으로 업데이트 되면서 reference를 찾는게 너무 어려워서 도움을 청했는데, 너무 친절하게 설명해주셔서 해결할 수 있었다.

이전에 useEffect를 사용하는 방식이 아닌 새롭게 컴포넌트를 파서 거기서 실행되도록 했다.

// @/components/common

import { PropsWithChildren, useEffect, useState } from 'react';

const isAPIMockingMode = process.env.NEXT_PUBLIC_API_MOCKING === 'enabled';

const MSWProvider = ({ children }: PropsWithChildren) => {
  const [isReady, setIsReady] = useState(false);

  useEffect(() => {
    const init = async () => {
      if (isAPIMockingMode) {
        const init = await import('@/mocks/initMSW');
        await init.initMSW();

        setIsReady(true);
      }
    };

    if (!isReady) init();
  }, [isReady]);

  if (isAPIMockingMode && !isReady) return null;
  return <>{children}</>;
};

export default MSWProvider;

.env.mock파일을 만들고

//.env.mock
NEXT_PUBLIC_API_MOCKING=enabled

이렇게 넣어주었다.

기존에 uesEffect에서 하던 작업을 여기서 진행하고, _app.tsx에서 사용해 주었다.

// @/pages/_app.tsx
import '@/styles/globals.css';
import 'slick-carousel/slick/slick.css';
import 'slick-carousel/slick/slick-theme.css';
import type { AppProps } from 'next/app';
import React from 'react';
import { RecoilRoot } from 'recoil';
import MSWProvider from '@/components/common/MSWProvider';

export default function App({ Component, pageProps }: AppProps) {
  return (
    <RecoilRoot>
      <React.Suspense fallback={<div>Loading...</div>}>
        <MSWProvider>
          <Component {...pageProps} />
        </MSWProvider>
      </React.Suspense>
    </RecoilRoot>
  );
}

새롭게 적용하기

에러를 잡은 뒤 실제 데이터를 적용시켜 보았다. 기존 handler에서 파일을 분리 시켜서 mocking Data를 만들고 적용시켰다.

// @/mocks/dummy/mento/mentoMock.ts

import { HttpResponse, http } from 'msw';

const mentoData = [
  {
    id: 1,
    name: '이재진',
    introduct: '모애6기 회장입니다~',
    image: '',
    mainField: 'nextjs, front-end',
    career: '2023.6 ~ 2024.3 모던애자일6기',
    rank: 3,
    userReveiw: [
      {
        userId: 3,
        userName: '이제진',
        userImage: '',
        review: '너무 좋았습니다~',
        checkList: '1, 3, 2, 4',
      },
      {
        userId: 1,
        userName: '이재진',
        userImage: '',
        review: '너무 좋았습니다~',
        checkList: '4, 4, 4, 4',
      },
    ],
  },
  ...
];
  
export const userHandler = [
  //멘토리스트조회api
  http.get('/api/mento', () => {
    return HttpResponse.json(mentoData);
  }),
  //멘토정보조회api
  http.get('api/mento-unit', async ({ request }) => {
    const url = new URL(request.url);
    const productId = Number(url.searchParams.getAll('id'));
    const foundData = mentoData.filter((data) => data.id === Number(productId));
    if (!foundData) {
      return new HttpResponse(null, { status: 404 });
    }
    return HttpResponse.json(foundData);
  }),
];

get으로 리스트를 조회하고, param값으로 id를 넘기면, 해당 아이디에 맞는 정보를 불러오는 두개의 mock api를 만들고 진행했다.

// @/mocks/handler.ts

import { http, HttpResponse } from 'msw';
import { userHandler } from './dummy/mento/mentoMock';

export const handlers = [...userHandler];

이것 처럼 기존의 길던 로직을 치우고, import로 불러와서 때려 박아주었다.
새롭게 데이터가 추가되면 배열 뒤에 동일하게 ...새로운데이터형식으로 넣어주면 된다.


기존에 사용하던 json-server.json형태로 만들어서 사용하는 것 보다 좋았던 점은 내가 직접 로직을 짜고 해당 로직에 대해서 진짜 api요청과 비슷하게 할 수 있다는 점이 좋았다. 이 기능으로 백엔드에서 아직 구현되지 않았지만. 플론트에서도 작업을 할 수 있었던 것이 좋았다.

profile
나의 뇌를 Refactoring

0개의 댓글