Programming/Client

[MSW] 브라우저, Node.js 환경에서 API 모킹하기

Jiwoo 2023. 5. 22. 22:48

들어가며...

팀 프로젝트를 할 때 백엔드의 진행 속도가 프론트보다 느려서 API 완성을 기다리는 입장이었다.

당시 단순히 json 파일로 데이터를 모킹해서 사용하다보니 나중에 수정할 코드가 많았고

정상적으로 api 요청이 들어가고 있는건지 알 수 없는 문제가 있었다.

그래서 최근에는 msw를 사용해서 api를 모킹하고 있다.

🍀 MSW (Mock Service Worker)란?

서비스 워커(service worker)를 사용하여 네트워크 호출을 가로채는 API 모킹 라이브러리

실제 서버를 대신하여 브라우저 내에서 가상의 서버를 구축하여 개발 및 테스트할 수 있음

API 스펙은 나왔지만 아직 구현되지 않은 경우 임시로 사용 가능함


특징

  • 모킹이 네트워크 단에서 일어나기 때문에 프론트 코드를 실제 API와 통신하는 것과 크게 다르지 않게 작성할 수 있음
    => 완성된 API로 대체하기 쉬우며 프론트의 개발 생산성이 높아짐
  • 웹 브라우저 외에 테스트 환경(Node.js 기반)도 지원하고, 같은 핸들러를 공유할 수 있어서 작성 코드 최소화 가능

MSW 패키지 설치

npm i -D msw

Serveice Worker API란?

웹 응용 프로그램, 브라우저, 네트워크 사이의 프록시 서버 역할을 함

네트워크 요청을 가로채서 네트워크 사용 가능 여부에 따라 적절한 행동을 취함

=> 오프라인에서 온라인처럼 통신하는 경험 생성 가능

  • 워커 맥락에서 실행되기 때문에 DOM에 접근 불가
  • JS와는 다른 스레드에서 동작하므로 연산을 중단하지 않음 (논 블로킹)
  • 비동기적으로 동작하며, 동기적 XHR이나 웹 저장소 등의 API는 서비스 워커 내에서 사용 불가
  • 보안상의 이유로 HTTPS에서만 동작 (firefox의 사생활 보호 모드도 불가)

🍀 Node.js 환경에서 모킹하기

테스트 진행시 api 호출이 가능하려면 node.js 환경에서 모킹이 가능하도록

가상의 server를 만들어서 실행해야 함

기본 설정

jest.config.js 파일의“setupFilesAfterEnv”속성에 setupTests.ts 파일 추가

module.exports = {
 testEnvironment: 'jsdom',
 **setupFilesAfterEnv**: [
  '@testing-library/jest-dom/extend-expect',
  '<rootDir>/src/setupTests.ts', // 이 부분 추가
 ],
 // ...
};

server 생성

src 안에 mocks 폴더 생성 -> server.ts 파일 생성

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

import handlers from './handlers';

const server = setupServer(...handlers);

export default server;

테스트시 서버 실행 및 필요한 동작 세팅

src/setupTests.ts 파일 생성 및 설정

import server from './mocks/server';

// 모든 테스트 실행 전에 MSW 서버 실행
// onUnhandledRequest : 핸들러 없다면 에러내서 실수 방지
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));

// 모든 테스트 실행 후에 서버 닫기
afterAll(() => server.close());

// 각 테스트 실행 후에 서버 리셋
afterEach(() => server.resetHandlers());

Handler 만들기

server에서 네트워크 요청을 가로채서 실행할 handler 만들기

// src/mocks/handlers.ts
import { rest } from 'msw'; // 불러올 때 eslint 에러가 발생한다면, 위에 주석 추가하거나 설정 파일 만들기

const BASE_URL = 'http://localhost:3000';

const handlers = [
 rest.get(`${BASE_URL}/products`, (req, res, ctx) => {
  const products = [
   {
    category: 'Fruits', price: '$1', stocked: true, name: 'Apple',
   },
  ];
  // 베이스 객체 만들었다면 이렇게 불러와도 됨
  // const { products } = fixtures;

  // 위 url로 들어오는 요청은 모두 이렇게 반환됨
    return res(
      ctx.status(200), // 안써도 됨 (기본이 200임)
      ctx.json({ products }),
      // express라면 return ({ products }) 쓰면 끝이지만 이건 express가 아니기 떄문에 이렇게 작성
   );
  }),
 ];

export default handlers;

테스트 파일 작성

테스트 과정에서 파일에서 서버 통신을 하면 msw에서 가로채서

handlers에 설정한대로 동작하기 때문에 따로 mocking이 필요 없고,

실행을 기다리는 동작으로 감싸주면 됨


waitFor(fnc)

내부 함수가 동작할 때까지 기다렸다가 확인함

프로미스 기반 동작이기 때문에 async& await으로 감싸줘야함

// App.test.ts
import { render, screen, waitFor } from '@testing-library/react';

import App from './App';

// jest.mock 불필요

test('App', async () => {
 render(<App />);

 await waitFor(() => {
  screen.getByText('Apple');
 });
});

💡 테스트 환경에서 fetch가 작동하지 않을 경우

whatwg-fetch 설치해서 사용하기
참고 포스팅

🍀 브라우저 환경에서 모킹하기

위 코드만 설정하면 Node.js 환경에서만 서버가 실행되기 때문에

브라우저로 화면을 띄울 때는 데이터가 받아와지지 않음

브라우저 환경에서 API를 모킹할 때는 worker를 사용해야함

브라우저 서비스 워커에 등록하기

msw는 브라우저에서 서비스워커를 통해 작동하기 때문에 등록을 해야함

msw에서 제공하는 CLI 도구를 사용하면 쉽게 만들 수 있음

$ npx msw init public/ --save

💡 주의사항
public/ 부분에는 프로젝트에서 정적 리소스를 두는 폴더를 지정
보통 build, public, dist 등의 이름 사용
Create React App, NextJS, VueJS, Svelte.js, Vite 등 많은 라이브러리/프레임워크에서 public/을 그대로 사용 가능
(나는 parcel을 사용했기 때문에 dist로 변경함)

worker 생성

// src/mocks/workers
import { setupWorker } from 'msw';

import handlers from './handlers';

const worker = setupWorker(...handlers);

export default worker;

초기 실행 파일에서 worker 실행

// src/main.tsx
import ReactDOM from 'react-dom/client';
import App from './App';
import worker from './mocks/worker';

function main() {
  if (process.env.NODE_ENV === 'development') {
    worker.start();
  }

  const container = document.getElementById('root');
  if (!container) {
    return;
  }

  const root = ReactDOM.createRoot(container);
  root.render(<App />);
}

main();