본문 바로가기
개발/테스트코드

MSW(Mock Service Worker) : 비동기로 테스트하기

by 안뇽! 2023. 9. 1.
반응형

MSW(Mock Service Worker) : 비동기로 테스트하기

서버에서 데이터를 요청하는 경우 화면은 비동기로 그려진다. 예를들면 다음과 같은 컴포넌트가 있다.

import axios from "axios";
import { useEffect, useState } from "react";
import Row from "react-bootstrap/Row";

export default function Options({ optionType }) {
  const [items, setItems] = useState([]);

  useEffect(() => {
    // create an abortController to attach to the network request
    axios
      // attach abortController to request
      .get(`http://localhost:3030/${optionType}`)
      .then((response) => setItems(response.data))
      .catch((error) => {alert(error)});
  }, [optionType]);


  return (
    <>
      <Row>
        {items?.map((item) => <div>{item.name}</div>) || (
          <div>데이터가 없습니다.</div>
        )}
      </Row>
    </>
  );
}

위 컴포넌트는 localhost:3030에서 받아온 데이터를 화면에 렌더링하는 컴포넌트이다. 따라서 컴포넌트를 테스트할때도 서버 요청받는 시간을 기다려주어야 한다. 이를 어떻게 작성할 수 있을까?

MSW(Mock Service Worker)

프론트엔드 개발과 테스팅에 있어서 API 목업을 위한 도구다. MSW는 서비스 워커를 사용하여 실제 네트워크 요청을 가로채고, 미리 정의된 응답으로 모의 처리(mock handling)를 할 수 있게 해준다.

 

  • Node와 브라우저 두 환경 모두 사용 가능하기 때문에 프론트, 백 통합 테스팅에 사용할 수 있다.
  • 서비스워커기반으로 실제 네트워크 스택을 가로채기 때문에, 구성이나 라이브러리 수정 없이 실제 HTTP 요청을 목업할 수 있다.
  • 타입스크립트 호환 가능

 

1. 일단 npm install을 하자.

npm install msw --save-dev

2. 핸들러 만들기

특정한 url과 라우트에 무엇을 반환할지 결정

공식문서  에서 만들라는대로 만들자.

내용은 각자 상황에 맞게 작성.

// src/mocks/handlers.js
import { rest } from 'msw'

export const handlers = [
  rest.get("http://localhost:3030/scoops", (req, res, ctx) => {
    return res(
      ctx.json([
        { name: "Chocolate", imagePath: "/images/chocolate.png" },
        { name: "Vanilla", imagePath: "/images/vanilla.png" },
      ])
    );
  }),
]

3. 로컬에서 서버 만들기(테스트 해야 하니까)

알아서 만들기

4. MSW server.js 만들기(프론트,백 할때 백엔드 서버 아님)

공식문서를 따라 하자.

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

// This configures a request mocking server with the given request handlers.
export const server = setupServer(...handlers)

그 후 CRA로 만든 앱에서 기본적으로 딸려 있는 setupTest.js에 공식홈페이지의 코드를 추가하여 다음과 같이 되도록 한다.

setupTest.js를 변경하는 코드는 MSW가 네트워크 코드를 가로채 핸들러에서 설정한 응답을 반환하도록 하기 위함이다.

방금 만든 src/mocks/server.js를 import하고 있다.

// 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";

// src/setupTests.js
import { server } from "./mocks/server.js";
// Establish API mocking before all tests.
beforeAll(() => server.listen());

// Reset any request handlers that we may add during the tests,
// so they don't affect other tests.
afterEach(() => server.resetHandlers());

// Clean up after the tests are finished.
afterAll(() => server.close());

비동기로 그려지는 컴포넌트 테스트 코드

서버에서 받아온 데이터로 img를 그리는 컴포넌트를 테스트 하려 한다.

다음과 같이 getAllByRole을 사용하여 컴포넌트의 img를 탐색하면 img가 실제로 있고 없고와 상관없이 무조건 실패한다.

import { render, screen } from "@testing-library/react";
import Options from "../Options";

test("displays image for each scoop option from server", () => {
  render(<Options optionType="scoops" />);

  // find images
  const scoopImages = await screen.getAllByRole("img", { name: /scoop$/i });

});

이유는, get은 동기적으로 동작하며 요소가 화면에 있는지 없는지 확인하는 기능이기 때문이다. 요소가 없으면 get은 즉시 오류를 발생시킨다. 그래서 get대신 find에 async, await을 붙혀야 한다.

import { render, screen } from "@testing-library/react";
import Options from "../Options";

test("displays image for each scoop option from server", async () => {
  render(<Options optionType="scoops" />);

  // find images
  const scoopImages = await screen.findAllByRole("img", { name: /scoop$/i });

});

find는 내부적으로 waitFor을 사용하여 요소가 등장할때까지 기다리다가 일정시간을 초과하면 오류를 발생시킨다.

결론

  • 네트워크 요청을 테스트 하고 싶을땐 msw를 이용하자.
  • 비동기적으로 로드되는 데이터나 컴포넌트를 테스트할때는 find 이용

find,get,query 차이

 

find, get, query

find, get, query *이 내용은 chatGPT의 도움을 받아 작성하였습니다. find find 쿼리는 비동기적으로 동작하며 주로 비동기적으로 로드되는 컴포넌트나 엘리먼트를 테스트 할 때 사용된다. 내부적으로 wa

wnsdufdl.tistory.com

 

반응형