API 언제나와요? 하지말고 MSW

영근·2024년 9월 4일
1

FE Tools

목록 보기
1/1
post-thumbnail

Intro

업무 중 생각보다 UI 작업이 빠르게 끝나 부득이하게(?) "API 언제나와요?"를 외쳐야 할 경우가 있습니다.
프론트엔드 개발자 입장에서는 '어차피 API 나오면 이후엔 내 작업밖에 없는데..' 라는 마음이 들면서 조급해지긴 하지만, 백엔드 개발자 입장에서는 마음이 좋지는 않은가 봅니다.

그래서 MSW를 도입하기로 결정했습니다.



세팅하기

Next.js v12와 함께 사용하기 위해 세팅하였습니다.

설치

yarn add -D msw@latest

mockServiceWorker.js 생성

npx msw init public/ --save

client & server

Next.js 환경이기 때문에 서버 사이드도 고려하여 세팅해야 합니다.

// mocks/client.ts
import { setupWorker } from 'msw/browser';

import { handlers } from './handlers';

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

import { handlers } from './handlers';

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

handler

  • url 은 full URL이 아니고 path 만 적어줘도 됩니다.
// mocks/handler.ts
import { http, HttpResponse } from 'msw';

export const handlers = [
  http.get('/test', () => {
    console.log('msw captured');

    return HttpResponse.json({
      id: '1',
      name: '근영',
    });
  }),
];

init

  • 서버 / 클라이언트 분기를 나눠 실행하는 함수를 만들어 줍니다.
// mocks/index.ts
export async function initMsw() {
  if (typeof window === 'undefined') {
    const { server } = await import('./server');
    server.listen();
  } else {
    const { worker } = await import('./browser');
    worker.start();
  }
}
  • 개발 환경에서만 실행해야 하기 때문에 env를 하나 만들고, _app.tsx!
    에서 실행했습니다. (❗️최종 코드 아님❗️)
if (process.env.NEXT_PUBLIC_API_MOCKING === 'enabled') {
  initMsw();
}

function App() {...}

Troubles

module not found

Module not found: Package path ./browser is not exported from package /node_modules/msw (see exports field in /node_modules/msw/package.json)

Next.js 환경에서는 추가적으로 설정이 필요하여 발생하는 이슈입니다.
관련 이슈

아래와 같이 next.config.js 파일에 추가해주면 해결됩니다.

if (isServer) {
      if (Array.isArray(config.resolve.alias)) {
        config.resolve.alias.push({ name: 'msw/browser', alias: false });
      } else {
        config.resolve.alias['msw/browser'] = false;
      }
    } else {
      if (Array.isArray(config.resolve.alias)) {
        config.resolve.alias.push({ name: 'msw/node', alias: false });
      } else {
        config.resolve.alias['msw/node'] = false;
      }
    }
}

module not found : Can't resolve graphql

이리 저리 주석처리 하고 패키지를 까보니 handler 부분 http를 import 하는 부분에서 graphql을 import하고 있었습니다.

찾아보니 v1 -> v2 마이그레이션 문서에서 v2 부터 http 객체를 사용하고, 이전에는 rest 객체를 사용하는 것을 발견해서 버전을 1.3.3으로 낮추고 rest 객체를 사용하니 문제가 해결되었습니다.

어느 버전부터 안되는건지 버전을 쭉 살펴보니, latest 버전이 4일 전(9월 23일 기준) 퍼블리시 된 v2.4.1.이었는데, 해당 버전 package.json 파일에 변경점이 있는 것을 발견했습니다.

커밋을 살펴보니 graphql이 optional한 dependency로 수정되어있었습니다.

그리고 문제가 있는건 저뿐만이 아니었는지.. 올라온지 얼마 되지 않은 따끈따끈한 이슈를 발견했습니다.

결론은 이전 버전을 쓰자는 말이었고 .. 결국 v2.3.5로 버전을 낮춰 해결했습니다.


console warning : handler에서 정의하지 않은 request

위 두 가지 문제를 해결한 뒤, msw가 정상적으로 초기화되는 것을 확인한 뒤, 콘솔에 아래와 같은 경고가 많이 떠있었습니다.

[msw] warning: intercepted a request without a matching request handler

공식 문서를 찾아보니, handler에 정의되지 않은 요청들에 대한 설정의 기본 값이 'warn'으로 되어있어, 해당 값을 'bypass'로 변경하는 옵션을 추가하니 해결되었습니다.

export async function initMsw() {
  if (typeof window === 'undefined') {
    const { server } = await import('./server');
    server.listen({ onUnhandledRequest: 'bypass' }); // 옵션 추가
  } else {
    const { worker } = await import('./browser');
    await worker.start({ onUnhandledRequest: 'bypass' }); // 옵션 추가
  }
}

MSW 초기화와 렌더링 순서 문제(404)

이벤트로 API fetching을 하면 문제 없지만, 페이지가 렌더링될 때 fetching 하면 404가 뜨는 문제가 있었습니다.

콘솔을 살펴보니, 렌더링 하며 API fetching -> 이후 MSW 초기화 순으로 순서가 반대로 진행되어 일어나는 문제였습니다.

  • 렌더되며 GET /test 요청
  • 정의되지 않은 path 이기 때문에 404 에러
  • 이후 MSW 초기화 (mocking enabled)

따라서 렌더되기 전에 MSW를 초기화하기 위해 MSW 래퍼 컴포넌트를 만들었습니다.

// mocks/Msw.tsx
import { PropsWithChildren, useEffect, useState } from 'react';

import initMsw from '.';

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

const Msw = ({ children }: PropsWithChildren) => {
  const [ready, setReady] = useState(() => !mswEnabled);
  const init = async () => {
    await initMsw();
    setReady(true);
  };
  useEffect(() => {
    if (!ready) {
      init();
    }
  }, [ready]);

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

export default Msw;
// _app.tsx
<Msw>
  ...
  <Component {...pageProps} />
<Msw>

적용 후 해결되었습니다.


profile
Undefined JS developer

0개의 댓글