Next.js 프로젝트에 MSW 도입하기: API 호출 비용 절감하기

Sara Jo·2024년 10월 28일
0
post-thumbnail

현재 진행하고 있는 Next.js 프로젝트에서 MSW(Mock Service Worker)를 도입하게 되었다.

프로젝트에서 사용하고 있는 API가 호출할 때마다 비용이 발생하는 구조라서, 개발 및 테스트 단계에서 매번 실제 API를 호출하기에는 비용적으로 부담이 될 것 같았다. 이를 위해 실제 API를 호출하지 않고 가상의 응답을 제공하는 MSW를 도입하기로 결정했다. MSW를 사용해서 개발 및 테스트 환경에서 과금에 대한 걱정없이 API 호출할 수 있었고, 그 과정을 정리해보고자 한다.


MSW란?

MSW 공식문서
Mock Service Worker(MSW)는 브라우저나 Node.js 환경에서 네트워크 요청을 가로채고, 미리 정의된 가상의 응답을 제공하는 라이브러리다. 이를 통해 실제 API 서버에 요청을 보내지 않고도 애플리케이션을 개발하고 테스트할 수 있다. MSWService Worker를 기반으로 동작하며, 클라이언트 사이드와 서버 사이드 모두에서 사용 가능하다.


MSW를 사용하는 이유

  1. 비용 절감: 나의 경우와 같이 실제 API 호출마다 비용이 발생하는 경우에, MSW를 사용하면 개발 및 테스트 환경에서 실제 API 호출을 줄여 비용을 절감할 수 있다. (⭐️)
  2. 개발 효율성: 백엔드가 완전히 준비되지 않았더라도 프론트엔드 개발을 진행할 수 있다! (⭐️)
  3. 테스트 용이성: 다양한 가상 응답 시나리오를 사용하여 테스트할 수 있다.

Next.js (App Router) 프로젝트에 MSW 설정하기

1. MSW 설치

npm install msw@latest --save-dev

2. MSW 핸들러 정의하기

// src/mocks/handlers.ts
import { http, HttpResponse } from 'msw'
 
export const handlers = [
  // GET 요청을 가로채고 가상 응답을 제공
  http.get('https://example.com/user', () => {
    // 가상의 응답 데이터 반환
    return HttpResponse.json({
      id: 'c7b3d8e0-5e0b-4b0f-8b3a-3b9f4b3d3b3d',
      firstName: 'John',
      lastName: 'Maverick',
    })
  }),
]

3. Service Worker 파일 생성

MSW는 Service Worker를 사용하기 때문에, mockServiceWorker.js 파일을 생성해야 한다. 다음 명령어를 실행하면 public 디렉토리에 Service Worker 파일이 추가되고, package.json 파일에 msw.workerDirectory가 추가된다.

npx msw init public/ --save

4. 브라우저와 서버용 워커 설정

MSW는 브라우저와 Node.js 환경에서 각각 다른 방식으로 동작하기 때문에, 브라우저용과 서버용 워커를 설정해야 한다.

  • browser.ts: 브라우저 환경에서 MSW 워커 설정
// src/app/mocks/browser.ts
import { setupWorker } from "msw/browser";
import { handlers } from "./handlers";

export const worker = setupWorker(...handlers);
  • server.ts: Node.js 환경(예: 서버사이드 렌더링)에서 MSW 서버 설정
// src/app/mocks/server.ts
import { setupServer } from "msw/node";
import { handlers } from "./handlers";

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

5. MSW 초기화 함수 작성

// src/app/mocks/index.ts
export async function initMsw() {
  if (typeof window === "undefined") {
    const { server } = await import("./server");
    server.listen();
  } else {
    const { worker } = await import("./browser");
    await worker.start();
  }
}
  • initMsw함수는 실행 환경이 브라우저인지 Node.js인지 확인하여 해당 환경에 맞는 워커를 시작한다.
  • 브라우저 환경에서는 worker.start를 호출하여 워커를 시작하고, Node.js 환경에서는 server.listen을 호출하여 서버용 MSW를 시작한다.

6. MSW 컴포넌트 생성

// src/app/mocks/MSWComponent.tsx
"use client";

import { useEffect, useRef, useState } from "react";

export const MSWComponent = ({ children }: { children: React.ReactNode }) => {
  const [mswReady, setMswReady] = useState(false);
  const hasStartedRef = useRef(false);

  useEffect(() => {
    const init = async () => {
      if (hasStartedRef.current) return;
      hasStartedRef.current = true;

      const { initMsw } = await import("./index");
      await initMsw();
      setMswReady(true);
    };

    if (!mswReady) {
      init();
    }
  }, [mswReady]);

  return <>{children}</>;
};
  • MSWComponent는 최상위 컴포넌트로, MSW를 초기화하고 자식 컴포넌트를 렌더한다.
  • 콘솔에 아래 에러메시지가 발생해서, useEffect 훅을 사용해 MSW가 여러번 초기화되는 것을 방지했다.
[MSW] Found a redundant "worker.start()" call. Note that starting the worker while mocking is already enabled will have no effect. Consider removing this "worker.start()" call.

7. 레이아웃 파일에 MSW 컴포넌트 통합

// src/app/layout.tsx
import { MSWComponent } from "./mocks/MSWComponent";

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body>
        <MSWComponent>{children}</MSWComponent>
      </body>
    </html>
  );
}

RootLayout 컴포넌트 내에 MSWComponent를 포함시켜 애플리케이션 전체에서 MSW가 활성화되도록 했다.


8. MSW를 통해 API 호출하기

// src/app/page.tsx
import axios from "axios";
import { useState } from "react";


export default function Home() {
  const [data, setData] = useState();
  
  const callAPI = async () => {
    const apiURL = `${process.env.NEXT_PUBLIC_API_BASE_URL}/api`;
    setIsLoading(true);

    try {
      const response = await axios.get(apiURL);
      const data = response.data; 
      setData(data);  
    } catch (error) {
      console.error("Error fetching data: ", error);
    } finally {
      setIsLoading(false);
    }
  };
  
  ...
}

콘솔에 아래와같이 모킹이 활성화 되었다는 메시지가 뜨면 성공!🎉

0개의 댓글