[Refactoring] Chapter2 .msw로 가짜 데이터를 모킹해보자 (비동기 문제해결)

rlorxl·2024년 8월 26일
0

회고

목록 보기
4/7
post-thumbnail

리팩토링을 진행하기에 앞서 가장 큰 문제가 발생했다.

프로젝트에서 db로 planet scale과 prisma를 사용하고 있었는데 planet scale이 갑자기 유료화 되었다

가격을 지불하고 사용하려고 한달 정책을 봤는데 한달에 6만원대는 너무 가혹한 가격이었고 방법을 고민하다 Rest API를 임시로 구현할 수 있는 msw를 사용해서 로컬에서 데이터를 가져올 수 있도록 변경하기로 했다.

msw

msw는 Mock Service Worker의 약자로 브라우저와 node.js에서 API모킹을 구현할 수 있는 라이브러리이다. msw를 이용해 서버로의 요청을 가로채 지정한 가짜 응답을 응답으로 반환해줄 수 있다. 그리고 fetch(), axios, react query등 타사 라이브러리와 함께 작동이 가능하다.

보통 개발단계에서 API가 나오지 않았을 때 손놓고 있는 상황이 많이 생기는데, msw를 이용해서 실제 API요청의 구현을 동일하게 하는 목적으로 많이 도입한다고 한다.

시작하기

msw를 설치 후 서버와 브라우저에서 실행될 수 있도록 세팅해준다.

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

export const server = setupServer(...handlers);
// src/mocks/browser.ts
import { setupWorker } from 'msw/browser';
import { handlers } from './handlers';

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

index.ts에서는 서버, 브라우저 여부에 따라 다른 패키지를 실행시킬 수 있도록 작성한다.

// src/mocks/index.ts
const initMSW = async () => {
  if (typeof window === 'undefined') {
    const { server } = await import('./server');
    server.listen();
  } else {
    const { worker } = await import('./browser');
    worker.start({
      onUnhandledRequest: (req) => {
        if (req.url.startsWith('/_next/static/')) {
          return;
        }
      },
    });
  }
};

export { initMSW };

그리고 가장 중요한 handler를 작성해주면 된다.

import { http, HttpResponse } from 'msw';
import { products } from './data/products';

export const handlers = [
  http.get('/test', () => HttpResponse.json({ successText: '성공입니다😀'})),
  http.get('/api/products', () => HttpResponse.json(products)),
];

비동기 문제 발생

콘솔에 Mocking enabled는 정상적으로 찍혔지만 서비스 워커가 초기화된 후 지정한 응답을 찾지 못하고 404오류가 발생하는 문제가 생겼다.

구글링을 통해 문제해결법을 찾으려고 했지만 대부분 next13 버전의 서버/클라이언트 컴포넌트 파일과 관련된 글들이 많이 나왔었는데 나는 next12버전이라 서버 컴포넌트 관련 문제는 아니었고,

결론먼저 말하면 브라우저에서 msw를 초기화할 때, worker.start() 의 비동기 동작이 문제였다.

브라우저의 서비스워커 초기화

mocks/index.ts코드를 보면 브라우저에서 서비스워커를 초기화 시키는 코드가 있는데 서비스워커가 초기화되기 전에 앱이 렌더링되지 않도록 하기 위해 provider를 만들고 전체 앱 컴포넌트를 이provider로 감싸 조건이 부합할 때만 컴포넌트가 렌더링되도록 해주었다.

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

const MSWProvider = ({ children }: PropsWithChildren) => {
  const [isReady, setIsReady] = useState(false);
  
  useEffect(() => {
    const enableMocking = async () => {
      if (process.env.NEXT_PUBLIC_API_MOCKING !== 'enabled') return;
    
      await initMSW();
      setIsReady(true);
    }

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

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

export default MSWProvider;

이 때 provider에서 await으로 초기화 함수(initMSW)를 기다리는데 초기화 함수에서 worker.start()를 반환해주지 않아await initMSW()구문이 넘어가고 setIsReady가 바로 실행되어 서비스워커가 초기화되기 전에 컴포넌트가 렌더링되었고 response를 찾을 수 없는 오류가 발생한 것.

블로그 글들을 참고했을 때 초기화함수를 분리해서 사용한 경우에도 return문이 없는 경우가 많아서 놓쳤는데 docs의 예시코드를 보고 알았다. Conditionally enable mocking

// src/index.jsx
import React from 'react'
import ReactDOM from 'react-dom'
import { App } from './App'
 
async function enableMocking() {
  if (process.env.NODE_ENV !== 'development') {
    return
  }
 
  const { worker } = await import('./mocks/browser')
 
  // `worker.start()` returns a Promise that resolves
  // once the Service Worker is up and ready to intercept requests.
  return worker.start()
}
 
enableMocking().then(() => {
  ReactDOM.render(<App />, rootElement)
})

코드를 보면 worker.start()를 확실히 리턴해주고 있는걸 확인할 수 있다.

해결방법

const initMSW = async () => {
  if (typeof window === 'undefined') {
    const { server } = await import('./server');
    return server.listen();
  } else {
    const { worker } = await import('./browser');
    return worker.start({
      onUnhandledRequest: (req) => {
        if (req.url.startsWith('/_next/static/')) {
          return;
        }
      },
    });
  }
};

export { initMSW };

worker를 반환해주자 정상적으로 msw가 초기화된 후 지정한 response값을 확인할 수 있었다.



references

0개의 댓글