FrontEnd

[라이브러리] Mock Service Worker 시작하기

머털박사 2022. 8. 13. 16:43

들어가며

이 포스팅은 React TDD를 진행하기 위해 Mock 서비스를 만들고 싶어하는 사람을 대상으로 작성했습니다. MSW(Mock Service Worker)의 작동 방식과 설정 방법에 대해 다룹니다.

Mocking의 정의와 장단점

일단 Mocking이란 말 그대로 모조품이다. 실제 서비스처럼 작동하는 가짜 서버를 만드는 것이다. 굳이 가짜 서버를 만드는 이유는 아래 장점을 확인하면 쉽게 파악할 수 있다.

1, 2번이 Mocking을 사용하는 가장 주된 이유다. 프론트엔드 입장에서 백엔드 서버가 존재하지 않으면 UI나 테스트 코드를 작성하기 힘들다. 이때 사용하는 것이 바로 Mocking이다. 백엔드에 개발이 끝나길 멍하니 기다리지 않고 바로 프론트엔드 개발을 진행할 수 있다. 하지만 당연히 단점도 존재한다.

모의 서비스에 연결시킨 테스트나 UI개발을 다시 실제 서비스에 연결하려면 같은 작업을 반복해야할 것이다. 또 실제 서비스가 아니다보니 아키텍처, 기술 및 인프라가 다르기 때문에 이에 따른 동작이 완전히 똑같을 수 없다.

수 많은 Mocking 라이브러리 중 MSW를 선택하는 이유

Mock Service Worker는 Service Worker API를 사용하여 실제 요청을 대행 수신하는 API 모킹 라이브러리입니다.

  1. GraphQL API를 지원한다.
  2. 대부분 mocking 라이브러리는 rest 형식만 지원한다.
  3. 노드 환경과 브라우저 환경에서 동일한 요청 핸들러를 공유할 수 있다.
  4. 노드 환경은 테스트를 위해 필요하고 브라우저 환경은 개발과 디버깅 과정에서 필요하다. 만약 MSW를 사용하지 않는다면 노드환경용 서버와 브라우저 환경용 서버, 총 2개의 서버를 만들어야 한다.
  5. 기존의 코드를 변경할 필요 없다.
  6. 모킹 방식의 Json Server은 요청이 모의 서버의 끝점(endpoint)를 가리켜야 한다. Service Mocker은 빌드 워커 파일이 적절한 범위에 있어야하고 sw-loaderService-Worker-Allowed header를 지정해야한다.

MSW 작동 방식

일반적인 mocking 방법은 두 가지가 존재한다.

  1. 실제 서버를 대체하는 원시적인 모킹 서버
  2. 네트워크 요청을 가로채기 위해 네이티브 http, https XMLhttpRequest 모듈 스텁 (stub, 바꿔치기)

MSW는 이 두 가지 방법 모두 아니다. nock 라이브러리처럼 http같은 네트워크 모듈을 monkey patching하는 방식으로 구현한다. monkey patch란 런타임 환경에서 메모리 소스를 직접 변경하여 사용하는 기법이다.

브라우저에 서비스 워커를 등록하여 외부로 나가는 네트워크 리퀘스트를 감지한다. 그 요청을 실제 서버로 갈 때 중간에 가로채서(intercept) MSW 클라이언트 사이드 라이브러리로 보낸다. 그후 등록된 핸들러에서 요청을 처리한 후 모의 응답을 브라우저로 보낸다.실제 서버는 그대로 둔 채로 중간에 요청만 가로챈다.

[MSW 시작하기] 1. 라이브러리 설치

npm install msw --save-dev

# or

yarn add msw --dev

일단 msw 라이브러리부터 설치한다.

[MSW 시작하기] 2. 모크 폴더 생성

mkdir src/mocks

msw를 이용하기 위해 요청 핸들러, 브라우저 및 서버의 고유 설정 목록을 정의해야한다. 이 파일들을 관리하는 데 규칙은 없지만 공식문서에서는 API 모킹 관련 모듈을 단일 디렉토리에서 보관할 것을 권장한다. 그리고 관례적으로 이런 파일은 보통 mock 폴더를 생성하여 그 안에서 관리한다.

[MSW 시작하기] 3. 요청 핸들러 작성

src/mocks/handler.js

import { rest } from "msw";

export const handlers = [
// 상품
  rest.get("http://localhost:5000/products", (req, res, ctx) => {
    return res(
      ctx.json([
        {
          name: "America",
          imagePath: "/images/america.jpeg",
        },
        {
          name: "England",
          imagePath: "/images/england.jpeg",
        },
      ])
    );
  }),

];

mocks 폴더에 handler.js를 생성해준다. 이때 handler 타입은 Rest, Graphql 두가지가 있다. 이 두 개 문법 모두 실제로 API가 사용되는 방식과 유사하다. 위와 같은 rest 형식의 경우에는

  • rest.get()
  • rest.post()
  • rest.put()
  • rest.patch()
  • rest.delete()
  • rest.options()

위와 같은 이름의 메서드가 존재한다. 이 메서드 사용하면 해당 REST API 메서드와 일치하는 요청 핸들러가 자동으로 생성된다. 이러한 요청 핸들러는 첫번째 인자에는 url, 두 번째 인자에는 콜백 함수가 들어간다. 콜백 함수 인자에 넣는 값은 총 세가지다. req, res, ctx 관련 내용은 아래 표와 같다.

이름 설명
req 매칭 요청에 대한 정보
res 모의 응답을 생성하는 기능적 유틸리티
ctx 모의 응답의 상태 코드, 헤더, 본문 등에 도움이 되는 함수 그룹

[MSW 시작하기] 4. node.js 서버 환경 구성하기

src/mocks/server.js

import { setupServer } from "msw/node";
import { handlers } from "./handlers";

// mocking server 생성
export const server = setupServer(...handlers)

msw에서 제공하는 setupServer() 함수를 이용해서 인스턴스를 생성한다. 위에서 생성한 handlers 코드를를 setupWork()에 파라미터로 넘겨주면 서버가 생성된다.

[MSW 시작하기] 5. 테스트 서버 작성하기

setupTests.js

// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';

import { server } from './mocks/server';

beforeAll(() => server.listen());

afterEach(() => server.resetHandlers);

afterAll(() => server.close());

이후 테스트를 위한 서버 셋업 작업이 필요하다. 테스트를 위한 반복적으로 해야하는 작업있을 때는 afterEachbeforEach 훅을 사용한다.

딱 한 번만 실행되도 괜찮은 작업은 afterAllbeforeAll 사용한다. 서버를 여는 작업과 닫는 작업은 딱 한번만 필요하니 All 훅을 사용하고 handler에서 비동기로 정보를 가져오는 작업은 매번 필요하니 Each로 설정한다.

여기까지 마치면 설정이 끝났다.

MSW 사용방법

이후 npm run test 를 실행하면 자동으로 모크 서버가 실행된다. 평소에는 서버에서 데이터를 가져올 때랑 똑같이 사용하면 된다.

만약 에러 헨들링 관련 테스트를 하고 싶을 때는

import { server } from "../../../mocks/server";
import { rest } from "msw";

test("데이터를 가져올 때 에러가 생깁니다.", async () => {
    server.resetHandlers(
        rest.get(`${process.env.REACT_APP_API_URL}/products`, (req, res, ctx) =>
            res(ctx.status(500))
        )
    );

    render(<Type orderType="products" />);

    const errorBanner = await screen.findByTestId("error-banner");
    expect(errorBanner).toHaveTextContent("에러가 발생했습니다.");
});

위와 같은 식으로 직접 호출해서 상태값을 바꿔주면 된다.


참고 자료

Mock Service Worker

Setup and Teardown · Jest

[JEST] 📚 모킹 Mocking 정리 (jest.fn / jest.mock /jest.spyOn)

Mocking

Mock Service Worker로 만드는 모의 서버

Mocking으로 생산성까지 챙기는 FE 개발