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 이용
'개발 > 테스트코드' 카테고리의 다른 글
'Assertion<HTMLElement>' 형식에 'toBeInTheDocument' 속성이 없습니다.ts(2339) (vitest,pnpm,react,ts) (0) | 2023.10.07 |
---|---|
find, get, query (0) | 2023.09.01 |
queryByText와 getByText (0) | 2023.08.28 |
리액트 모달 컴포넌트 테스트 (0) | 2023.08.27 |
fireEvent와 userEvent 차이 (0) | 2023.08.26 |