ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Mocking] MSWjs를 이용한 API 모킹 쉽고 빠르게 활용하는 법(with React + Webpack)
    frontend/react&next 2022. 10. 27. 16:17

    오늘은 많이들 사용하는 Mocking 라이브러리인 MSW에 대해서 빠르게 파악해볼까 합니다. 아직 모킹을 활용한 개발을 해보신 적이 없으신 분들은 이런 경험이 있을 겁니다. "요청 드린 API언제 나와요?" 이처럼 협업을 하는데 있어서 시간낭비가 발생하게 되는 경우가 많으실 겁니다. 이럴때 사용하는게 API목업 입니다. 개발 시작 전 서버개발자와의 협의를 통하여 통신에 대한 인터페이스를 정의하고, 이에 맞춰 미리 API목업을 하여 서버의 진척상황과 상관없이 프로젝트를 진행할 수 있습니다.  

     

    이런 API목업 라이브러리 중 MSW에 대해서 알아보도록 하겠습니다. MSW는 말 그대로 Mock Service Worker입니다. 그래서 아주 자연스럽게 기존의 API호출과 연동하여 개발환경에 따라 셋팅할 수 있습니다. 서비스 워커를 사용하지 않는 Mocking 라이브러리에는 mirageJS와 같은 것들이 있습니다. 우린 MSWjs에 대해서 한번 해보겠습니다. 이번 예시는 브라우저에서 사용하지만 MSW는 브라우저와 노드 모두 지원합니다.

     

    1. 설치

    npm install msw --save-dev
    # or
    yarn add msw --dev

    가장 먼저 msw를 설치하여 줍니다. 환경에 맞게 설치하시면 됩니다.

     

    2. 초기화

    npx msw init <PUBLIC_DIR> --save
    //저는 public dir이 최상단에 있어서 아래와 같이 진행했습니다.
    npx msw init public/ --save

    저는 public 경로를 제가 직접 셋팅하여 사용하고 있기 때문에 위와 같이 작업하였습니다. 

    환경 경로
    Create React App ./public
    GatsByJS ./static
    NextJS ./public
    VueJS ./public
    Angular ./src (and add it to the assets of the angular.json file)
    Preact ./src/static
    Ember.js ./public
    Svelte.js ./public
    SvelteKit ./static
    Vite ./public

    CRA나 기타 프레임워크 별로 public의 위치가 다를 수 있으니 경로에 맞게 지정해주시면 됩니다. 위는 환경별 기본 지정 경로 입니다.

    mockServiceWorker.js in public

    이까지 잘 따라오셨으면 아래와 같이 파일이 생긴것을 확인하실 수 있습니다.

     

    3. 셋팅

    이제 웹팩과 같은 번들러에서 설정해놓은 entry파일로 가서 아래와 같이 코드를 작성합니다.

    // ./index.tsx
    import { createRoot } from "react-dom/client";
    import Root from "@/Root";
    import "@/styles/index.css";
    
    
    // START
    if (process.env.NODE_ENV === "development") {
      const { worker } = require("./libs/api/mocks/browser");
      worker.start();
    }
    // END
    
    const container = document.getElementById("root");
    const root = createRoot(container as Element);
    
    root.render(<Root />);

    위의 코드는 보시다 싶히 환경이 개발일때 Mock Service Worker를 시작하는 것입니다. 그럼 설정해놓은 모킹 API들이 동작하게 됩니다. 이제 저 worker를 불러오기 위한 코드를 확인하겠습니다.

    // ./libs/api/mocks/browser.ts
    
    import { setupWorker } from "msw";
    import { handlers } from "./handlers";
    
    export const worker = setupWorker(...handlers);

    msw의 setupWorker를 import해서 export하고 인자로 handlers를 넘겨줍니다. 여기서 setupWorker함수는 client-side모킹을 위해 존재하는 함수입니다.

    // ./libs/api/mocks/handlers.js
    
    import { rest } from "msw";
    import { mockWrite, mockRead } from "./resolvers/mockBokboot"
    
    export const handlers = [
      rest.post("/write", mockWrite),
      rest.post("/read", mockRead),
    ];

    저는 쓰기와 읽기라는 두가지 리졸버를 만들었고 해당 리졸버를 REST API의 POST로 선언하여 핸들러에 넣어줬습니다. 뭐 한두가지 일때는 리졸버를 따로 빼지 않고 핸들러 내부에서 다 작성하셔도 무방하지만, 서비스를 유지보수하다보면 확장가능성을 열어두고 하셔야 되기 때문에 규모가 커질것을 데뷔하여 리졸버에 나눠서 작성하여 줍니다.

    // ./libs/api/mocks/resolvers/mockBokboot.ts
    
    import {
      DefaultBodyType,
      PathParams,
      ResponseComposition,
      RestContext,
      RestRequest,
    } from "msw";
    
    interface CodeProps {
      code: string;
    }
    
    interface IdProps {
      id: string;
    }
    
    const mockWrite = async (
      req: RestRequest<CodeProps, PathParams<string>>,
      res: ResponseComposition<DefaultBodyType>,
      ctx: RestContext
    ) => {
      const { code } = req.body;
    
      if (!code)
        return res(
          ctx.status(500),
          ctx.json({ message: "Please, Enter the code." })
        );
    
      return res(
        ctx.status(200),
        ctx.json({ queryParam: "?id=1", message: "Code copy was succesful." })
      );
    };
    
    const mockRead = async (
      req: RestRequest<IdProps, PathParams<string>>,
      res: ResponseComposition<DefaultBodyType>,
      ctx: RestContext
    ) => {
      const { id } = req.body;
      if (!id)
        return res(
          ctx.status(500),
          ctx.json({ message: "Please, Enter Code Id." })
        );
      return res(
        ctx.status(200),
        ctx.json({ code: 'console.log("hi")', message: "Code load was successful" })
      );
    };
    
    export { mockWrite, mockRead };

    전 위와 같이 간단한 쓰기와 읽기 리졸버를 만들었습니다. 내부에서 원하는 로직을 작성하셔서 상태와 body를 반환하여 주면 됩니다. browser, handler, resolver를 한세트로 하여 작성해나가시면 될 겁니다. 이렇게 모킹을 위한 기본적인 셋팅을 끝냈습니다.

     

    4. 활용

    npm i axios
    //or
    yarn add axios

    API 호출을 위해 Axios를 설치해 보겠습니다. 그 후 axios를 사용하기 위한 기본적인 셋팅을 하겠습니다.

    // ./libs/api/apiConfig.ts
    
    import axios, { AxiosRequestConfig } from "axios";
    
    const HOST_URL =
      process.env.NODE_ENV === "development"
        ? "/"
        : process.env.PRODUCTION_HOST_URL;
    
        
    const API_CONFIG: AxiosRequestConfig = {
      baseURL: HOST_URL,
      withCredentials: true,
    };
    
    const apiConfig = axios.create(API_CONFIG);
    
    export default apiConfig;

    위와 같이 환경에 따라 호출할 URL을 분기하여 줍니다. 개발 환경일때는 그럼 바로 "/" 이 경로를 기반으로 하여 호출 하게 됩니다. 그 다음 이렇게 셋팅된 axios를 좀 더 편리하게 사용하기 위해 아래와 같은 코드를 작성합니다.(안하셔도 됩니다. 저는 대체로 이렇게 합니다.)

    // ./libs/api/index.ts
    
    import apiConfig from "./apiConfig";
    
    const service = {
      get: (url: any, params: any) => {
        return apiConfig({
          url,
          params,
          method: "GET",
        }).then((res) => res.data);
      },
      post: (url: any, data: any) => {
        return apiConfig({
          url,
          data,
          method: "POST",
        }).then((res) => res.data);
      },
      delete: (url: any, data: any) => {
        return apiConfig({
          url,
          data,
          method: "DELETE",
        }).then((res) => res.data);
      },
      put: (url: any, data: any) => {
        return apiConfig({
          url,
          data,
          method: "PUT",
        }).then((res) => res.data);
      },
    };
    
    export default service;

    여기 까지 셋팅이 끝났으면 운영과 개발을 넘나들며 편하게 개발할 수 있는 셋팅이 끝났습니다. 이제 마지막으로 PRODUCTION_URL을 ENV파일에 적어줍니다.

    //최상단 package.json과 동일한 위치
    //.env
    PRODUCTION_HOST_URL=운영 URL

    위와 같이 모든 설정이 끝났으면 이제 실제 사용을 해보도록 하겠습니다.

    service.post("/write", { code: "123" }).then((result) => {
      console.log(result);
    });

    위와 같이 write를 호출하게 되면 개발환경에서 실행을 하게 되면 msw에 설정해둔 write리졸버를 타게 되고 다음과 같은 결과를 얻을 수 있습니다.

    콘솔로 확인한 결과
    네트워크 탭에서 확인한 결과

    여기까지 우리는 MSWjs를 이용하여 간단한 호출과 응답을 받아보았습니다. 이처럼 MSWjs를 사용한다면 많은 이점들이 있습니다. 특히 협업에 있어서는 많은 이점에 있다고 생각합니다. 글 중간중간에 최대한 제가 이렇게 쓰는 이유들을 넣어보려고 하였는데 많이 부족할 수도 있습니다.

     

    실제로 사이드 프로젝트에서 제가 사용하고 있는 모습을 확인하시고 싶으시면 "샘플 source  링크"를 통해서 확인하고 참고하여 개발하면 도움이 될 것이라고 생각합니다.

    반응형

    댓글

Designed by Tistory.