You are currently viewing React Hooks 테스트 방법

React Hooks 테스트 방법

이 글은 LogRocket 블로그의 글을 공부하며 한글로 옮긴 것입니다. 저작권에 관련된 문제가 있다면 알려주시면 감사하겠습니다.

React

여기에서는 Jest, Enzyme, 그리고 React Testing Library라는 세 가지 도구에 대해 좀 더 자세히 설명하고 있어요. 이 도구들은 웹사이트를 만드는 데 사용되는 언어인 React로 만들어진 부분들이 잘 작동하는지 확인하는 데 도움을 줍니다. 아이작 씨는 이 글에서 React에서 새로 생긴 기능인 Hooks를 테스트하다가 발생할 수 있는 여러 가지 문제들과 그 해결책에 대해서도 얘기하고 있어요. 또, React의 최신 버전인 v18과 관련된 새로운 정보도 공유하고 있답니다. React를 사용해서 웹사이트를 만드는 사람들에게는 꽤 유용한 내용이 될 거예요.

이야기를 시작하기 전에, 여러분이 알아두면 좋을 소식이 하나 있어요. React라는 컴퓨터 언어에서 Hooks라고 불리는 새로운 기능이 나왔는데, 많은 사람들이 이걸 매우 좋아했답니다. 이 새 기능 덕분에 사람들은 웹사이트를 더 쉽고 효율적으로 만들 수 있게 되었어요.

그런데, 웹사이트를 만들 때 그냥 예쁘기만 하고, 잘 작동하지 않으면 안 되잖아요? 그래서 ‘테스트’라는 과정이 정말 중요해요. 테스트를 통해, 만든 웹사이트가 잘 작동하는지, 오류는 없는지 확인할 수 있거든요.

이 글에서는, 특히 React의 새 기능인 Hooks를 어떻게 효율적으로 테스트할 수 있는지에 대한 팁을 주고 있어요. Jest, Enzyme, 그리고 React Testing Library라는 도구들을 사용하는 방법을 설명하고 있죠. 이 도구들은 마치 의사가 건강을 체크하기 위해 여러 가지 도구를 사용하는 것처럼, 웹사이트가 잘 작동하는지 확인하는 데 도움을 줍니다.

웹사이트를 만드는 팀이나 회사에서는 이런 테스트 과정을 통해 만든 제품에 대한 자신감을 얻게 돼요. 그래서, 이 글은 웹사이트를 만드는 사람들에게 React Hooks를 어떻게 잘 테스트할 수 있는지에 대한 유용한 가이드를 제공하고 있답니다.

여러분, 컴퓨터에서 웹사이트를 만들 때 쓰는 하나의 방법에 대해 소개해 드리려고 해요. 이걸 ‘리액트 훅(React Hooks)’이라고 부르는데요, 간단히 말해서, 이건 웹 페이지를 더 쉽게 만들 수 있게 도와주는 도구랍니다.

예전에는 웹사이트를 만들 때 ‘클래스’라고 하는 복잡한 코드를 많이 써야 했어요. 그런데, 이 ‘클래스’들 때문에 코드가 복잡해지고, 같은 일을 하는 코드가 여러 번 반복되기도 했죠. 이런 문제들을 해결하기 위해 ‘리액트 훅’이라는 새로운 방법이 나왔어요.

리액트 훅이란?

‘리액트 훅’을 사용하면, 이전보다 훨씬 간단하게 웹 페이지의 ‘상태'(예를 들어, 사용자가 어떤 버튼을 눌렀는지)를 관리할 수 있게 되요. 또, 웹 페이지가 어떻게 보여지고 작동해야 하는지를 정하는 ‘라이프사이클’이라는 것도 더 쉽게 다룰 수 있게 됩니다.

React Hooks를 이용한 React 앱 구축 방법

https://codesandbox.io/p/sandbox/pwwzw72l10

이제 ‘리액트 훅’을 사용해서 실제로 어떻게 웹사이트를 만들 수 있는지 보여드릴 거예요. 예를 들어, F1 레이스와 매년 우승자를 보여주는 웹사이트를 만들어볼 거예요. 이걸 만들면서 ‘리액트 훅’이 얼마나 유용한지 직접 보실 수 있을 거예요. 이 웹사이트를 만드는 과정을 한 번 따라해보시면, ‘리액트 훅’을 사용하는 게 어렵지 않다는 걸 알게 되실 거예요!

우리가 만든 앱에서, ‘useState’와 ‘useEffect’라는 두 가지 도구, 또는 ‘훅(Hooks)’을 사용해요. 이걸 이해하기 쉽게 설명해 드릴게요.

먼저 ‘useState’라는 훅을 사용해 볼게요. 이건 마치 메모장에다가 우리가 알아두고 싶은 정보를 적어두는 것과 비슷해요. 예를 들어, F1 레이스의 목록을 적어두거나, 어떤 레이스에서 누가 이겼는지 적어둘 수 있죠. ‘useState’를 사용하면, 이런 정보를 쉽게 적어두고, 필요할 때마다 바꿀 수도 있어요.

그 다음에는 ‘useEffect’라는 훅을 사용해요. 이건 마치 우리가 어떤 정보를 찾기 위해 서랍을 열어보는 것과 비슷해요. ‘useEffect’를 사용하면, 인터넷에서 F1 레이스의 결과 같은 정보를 가져올 수 있어요. 앱이 처음 시작될 때, ‘useEffect’는 자동으로 이 정보를 찾아서 우리의 메모장에다가 적어줍니다.

이런 식으로, ‘useState’와 ‘useEffect’를 사용해서 우리는 F1 레이스의 결과를 보여주는 앱을 만들 수 있어요. ‘useState’는 우리가 알아둬야 할 정보를 메모하는 곳이고, ‘useEffect’는 필요한 정보를 찾아서 그 메모장에 적어주는 역할을 한답니다. 이렇게 해서 우리는 필요한 정보를 쉽게 찾아볼 수 있는 앱을 만들 수 있게 되는 거죠!

앱에서 ‘useState’와 ‘useEffect’라는 두 가지 ‘훅(Hooks)’을 사용하는 방법을 살펴보겠습니다. 이것들은 React에서 새로운 기능을 추가하거나 데이터를 관리하는 데 도움을 줍니다.

useState 사용하기

// 레이스 목록을 빈 배열로 설정합니다.
const [races, setRaces] = useState([]);

// 특정 연도의 승자를 설정합니다.
const [winner, setWinner] = useState();

여기서 ‘useState’는 정보를 저장하는 작은 상자와 같습니다. 예를 들어, useState([])는 빈 배열을 저장하는 상자를 만들고, 이 상자에는 레이스 목록이 담길 거예요. const [races, setRaces] = useState([]); 이 코드는 레이스 목록을 저장할 상자(races)와 이 상자의 내용을 바꿀 수 있는 열쇠(setRaces)를 제공합니다.

useEffect 사용하기

useEffect(() => {
  fetch(`https://ergast.com/api/f1/2018/results/1.json`)
    .then(response => response.json())
    .then(data => {
      setRaces(data.MRData.RaceTable.Races);
    });

  fetch(`https://ergast.com/api/f1/2018/driverStandings.json`)
    .then(response => response.json())
    .then(data => {
      const raceWinner = data.MRData.StandingsTable.StandingsLists[0].DriverStandings[0].Driver.familyName + " " + data.MRData.StandingsTable.StandingsLists[0].DriverStandings[0].Driver.givenName;
      setWinner(raceWinner);
    });
}, []);

‘useEffect’는 마치 앱이 시작될 때 자동으로 실행되는 마법의 주문 같아요. 위 코드에서 ‘useEffect’는 인터넷에서 F1 레이스의 데이터를 가져옵니다. 첫 번째 fetch 함수는 레이스 목록을 가져오고, 두 번째 fetch 함수는 우승자의 이름을 가져옵니다. 가져온 정보는 앞서 만든 ‘useState’ 상자에 저장됩니다.

이 코드를 통해 우리는 앱에 필요한 데이터를 인터넷에서 가져와서 사용자에게 보여줄 수 있게 됩니다. 이렇게 ‘useState’와 ‘useEffect’를 사용하면, 복잡한 기능도 쉽게 구현할 수 있어요.

Jest가 뭐죠?

Jest는 마치 웹사이트나 앱이 잘 작동하는지 체크하는 조수와 같은 역할을 하는 도구예요. 자바스크립트로 만들어진 프로그램들을 쉽게 점검할 수 있도록 도와주죠. 그뿐만 아니라, React나 Vue 같은 프레임워크로 만들어진 앱들도 테스트할 수 있어요. 이 도구는 테스트를 꼼꼼하게 분리해서 각각 진행할 수 있게 해주고, 코드가 예상대로 작동하는지 확인하는 ‘스냅샷’ 기능도 제공해요. 게다가, 코드의 어떤 부분이 테스트에 사용되었는지, 또 얼마나 잘 커버되었는지도 알려줘서 매우 유용하답니다.

Jest with Enzyme 또는 React Testing Library를 사용하는 이유는 무엇입니까?

Jest를 Enzyme이나 React Testing Library와 같이 사용하는 이유는, 이 둘은 웹 페이지의 구조와 요소들을 살펴보는 데 특화되어 있지만, 테스트의 성공 여부를 판단하는 ‘주장(assertion)’ 기능은 제공하지 않기 때문이에요. Jest는 바로 이 ‘주장’을 처리해주는 테스트 러너 역할을 해요. 즉, 테스트가 잘 진행되었는지, 어떤 부분에 문제가 있는지를 알려주죠. 이렇게 함으로써, Jest는 React 앱을 테스트하는 데 필수적인 도구가 되었어요. 사용하기 쉽고, 다양한 기능을 제공하며, 많은 사람들이 사용하고 지원하는 커뮤니티도 있어서 많은 개발자들이 선호하는 테스트 프레임워크가 되었답니다.

What is Enzyme?

Enzyme은 React로 만든 웹 페이지의 작은 부분들을 쉽게 검사하고 다룰 수 있게 도와주는 도구예요. 마치 의사가 환자를 진찰할 때 여러 가지 도구를 사용하는 것처럼, 개발자들도 Enzyme을 사용해서 자신들이 만든 웹 페이지가 잘 작동하는지, 예상대로 보이는지를 확인할 수 있답니다.

Enzyme의 몇 가지 주요 기능은 다음과 같아요:

  • Shallow rendering: 이 기능은 현재 컴포넌트만을 렌더링해서 검사할 수 있게 해줘요. 마치 나무에서 잎사귀 하나만을 들여다보는 것처럼, 이 기능을 사용하면 전체 나무(웹 페이지)를 다 보지 않고도 한 부분만 집중해서 검사할 수 있어요. 이는 컴포넌트 하나하나를 독립적으로 검사하고 싶을 때 유용해요.
  • Full DOM rendering: 이 기능은 웹 페이지의 모든 부분을 렌더링해서, 컴포넌트들이 서로 어떻게 상호작용하는지를 볼 수 있게 해줍니다. 마치 의사가 전신 검사를 하는 것처럼, 이 기능을 사용하면 더 많은 정보를 얻을 수 있어요.
  • Mocking functions and components: 때때로, 특정 부분을 검사할 때 주변 환경을 조금 단순화하고 싶을 수 있어요. 마치 복잡한 기계에서 한 부품만을 떼어내서 검사하듯이, 이 기능을 사용하면 특정 컴포넌트나 기능을 격리해서 검사할 수 있어요.

이런 기능들 덕분에 Enzyme은 React로 만든 웹 페이지나 앱을 검사하고 테스트하는 데 매우 유용한 도구가 됩니다. 이를 통해 개발자들은 자신들의 작업물이 제대로 작동하는지, 사용자들이 예상대로 웹 페이지를 볼 수 있는지를 쉽게 확인할 수 있어요.

React Hooks를 Enzyme으로 테스트하기 위해, 우리는 먼저 ‘Create React App’을 사용하여 프로젝트를 만들어 볼 거예요. 시작하려면 아래의 단계를 따라 해보세요:

  1. 새로운 React 앱을 만들기 위해 이 명령어를 입력해주세요:
   npx create-react-app my-app
  1. 생성된 앱 폴더로 이동합니다:
   cd my-app

다음으로, Enzyme이라는 테스트 도구와 React용 어댑터를 설치해야 해요. 이를 위해 다음 명령어를 입력해주세요:

npm i --save-dev enzyme enzyme-adapter-react-16

이제 ‘src’ 폴더 안에 ‘setupTests.js’라는 파일을 만들고, 아래의 코드를 추가하여 Enzyme 어댑터를 설정해주세요:

import Enzyme from "enzyme";
import Adapter from "enzyme-adapter-react-16";

Enzyme.configure({ adapter: new Adapter() });

이 ‘setupTests.js’ 파일에 있는 코드는 우리의 테스트가 실행되기 전에 먼저 실행되요. 이 과정을 통해, Enzyme이 우리의 React 앱과 잘 연동되어 테스트를 수행할 수 있도록 준비하는 거죠. 이렇게 준비를 마치면, React 컴포넌트나 Hooks를 테스트하는 데 필요한 기반 작업이 모두 완료된다고 보면 돼요!

Testing the useState Hook with Enzyme

React의 useState 훅을 테스트해 보기 위해, 우리는 app.js 파일을 다음과 같이 업데이트할 거예요:

import React from "react";

const App = () => {
  const [name, setName] = React.useState("");

  return (
    <form>
      <div className="row">
        <div className="col-md-6">
          <input
            type="text"
            placeholder="Enter your name"
            className="input"
            onChange={(e) => {
              setName(e.target.value);
            }}
          />
        </div>
      </div>
      <div className="row">
        <div className="col-md-6">
          <button
            type="submit"
            className="btn btn-primary"
          >
            Add Name
          </button>
        </div>
      </div>
    </form>
  );
};

export default App;

여기서 우리는 기본적인 입력 필드와 버튼 요소를 가지고 있어요. 우리가 React.useState()를 사용한 방식에 주목하세요. 이것은 Enzyme 테스트에서 useState 훅을 모의(Mock)하기 위해 필요해요:

import React from "react";
import { shallow } from "enzyme";
import App from "./App";

const setState = jest.fn();
const useStateSpy = jest.spyOn(React, "useState");
useStateSpy.mockImplementation((initialState) => [initialState, setState]);
const wrapper = shallow(<App />);

여기서 우리는 성공적으로 useState 훅을 모의하였고, 입력 변경에 따른 상태 업데이트를 다음과 같이 테스트할 수 있습니다:

it("should update state on input change", () => {
  const newInputValue = "React is Awesome";
  wrapper
    .find(".input")
    .simulate("change", { target: { value: newInputValue } });
  expect(setState).toHaveBeenCalledWith(newInputValue);
});

Enzyme은 React Hooks를 지원하지만, React의 샬로우 렌더러에서 발생하는 상류 문제로 인해 .shallow()에서 몇 가지 단점이 있습니다. React 샬로우 렌더러를 사용할 때, useEffect()useLayoutEffect()는 호출되지 않습니다.

Testing React Hooks with React Testing Library

React Testing Library는 React 컴포넌트를 테스트하기 위한 간단한 해결책입니다. 이 도구는 react-dom과 react-dom/test-utils를 확장하여 가벼운 유틸리티 함수들을 제공하며, 여러분의 React 컴포넌트가 실제 사용되는 방식과 유사한 테스트를 작성하도록 장려합니다. React Testing Library의 주 목표는 개발자들이 자신의 테스트에 대해 더 자신감을 갖게 하여, 사용자가 컴포넌트를 사용하는 방식으로 테스트할 수 있게 하는 것입니다. CRA(Create React App)에 이미 설치되어 있어서, React용으로 흔히 사용되는 테스트 라이브러리가 되었습니다.

이제 React Testing Library를 사용하여 Hooks에 대한 테스트를 작성하는 예를 살펴보겠습니다. 위의 앱에서는 useState, useEffect, 그리고 useRef라는 세 가지 유형의 Hooks를 사용하고 있으며, 이 모든 것들에 대한 테스트를 작성할 것입니다.

useRef Hook 구현에는 useRef를 사용하여 ref 인스턴스를 생성하고 이를 입력 필드에 설정하는 과정이 포함됩니다. 이를 통해 ref를 통해 입력 필드의 값을 접근할 수 있게 됩니다. 한편, useEffect Hook 테스트는 주로 localStorage에 이름 상태 값을 설정하는 것과 관련이 있습니다.

이제 위의 구현들에 대한 테스트를 작성해 보겠습니다. 우리가 확인할 테스트는 다음과 같습니다:

  • 초기 카운트 상태가 0인지
  • 증가 및 감소 버튼이 작동하는지
  • 입력 필드를 통해 이름을 제출하면 이름 상태 값이 변경되는지
  • 이름 상태가 localStorage에 저장되는지

__tests__ 폴더로 이동하여 테스트 스위트가 포함된 hooktest.js 파일과 아래의 코드 줄을 확인하세요:

// hooktest.js
import { render, fireEvent, getByTestId } from "react-testing-library";

이 코드에서:

  • render는 컴포넌트를 렌더링하는 데 도움을 줍니다. document.body에 추가된 컨테이너로 렌더링합니다.
  • getByTestIddata-TestId로 DOM 요소를 가져옵니다.
  • fireEvent는 DOM 이벤트를 “발생”시키는 데 사용됩니다. 문서에 이벤트 핸들러를 붙이고 이벤트 위임을 통해 일부 DOM 이벤트를 처리합니다(예: 버튼 클릭).

다음은 hooktest.js 파일에 추가할 테스트 스위트입니다:

// hooktest.js

it("App loads with initial state of 0", () => {
  const { container } = render(<App />);
  const countValue = getByTestId(container, "countvalue");
  expect(countValue.textContent).toBe("0");
});

이 테스트는 getByTestId 헬퍼를 사용하여 요소를 가져온 다음, expect()toBe() 함수를 사용하여 내용이 0인지 확인함으로써 초기 카운트 상태가 0으로 설정되었는지를 확인합니다.

다음으로, 증가 및 감소 버튼이 작동하는지 확인하기 위한 테스트를 작성할 것입니다.

위의 hooktest.js 테스트 코드에서, 우리는 증가(increment) 및 감소(decrement) 버튼이 제대로 작동하는지를 확인하고 있어요. 즉, 증가 버튼을 클릭하면 숫자가 1이 되고, 감소 버튼을 클릭하면 다시 0이 되는지를 검사하고 있습니다.

// hooktest.js

it("증가 및 감소 버튼이 작동합니다", () => {
  const { container } = render(<App />);
  const countValue = getByTestId(container, "countvalue");
  const increment = getByTestId(container, "incrementButton");
  const decrement = getByTestId(container, "decrementButton");
  expect(countValue.textContent).toBe("0");
  fireEvent.click(increment);
  expect(countValue.textContent).toBe("1");
  fireEvent.click(decrement);
  expect(countValue.textContent).toBe("0");
});

다음 단계로, 입력 필드를 통해 이름을 제출하면 이름 상태 값이 실제로 변경되는지, 그리고 성공적으로 localStorage에 저장되는지를 확인하는 테스트를 작성할 것입니다:

// hooktest.js

it("입력 필드를 통해 이름을 제출하면 이름 상태 값이 변경됩니다", () => {
  const { container, rerender } = render(<App />);
  const nameValue = getByTestId(container, "namevalue");
  const inputName = getByTestId(container, "inputName");
  const submitButton = getByTestId(container, "submitRefButton");
  const newName = "Ben";
  fireEvent.change(inputName, { target: { value: newName } });
  fireEvent.click(submitButton);
  expect(nameValue.textContent).toEqual(newName);
  rerender(<App />);
  expect(window.localStorage.getItem("name")).toBe(newName);
});

위의 테스트 검증에서, 우리는 fireEvent.change 메소드를 사용해 입력 필드에 값을 입력한 후, submitButton을 클릭하는 이벤트를 발생시켰어요.

테스트는 버튼 클릭 후 ref의 값이 newName과 같은지 확인합니다. 마지막으로, rerender 메소드를 사용해 앱을 다시 로드하는 것을 시뮬레이션하고, 이전에 설정한 이름이 localStorage에 저장되었는지 확인합니다.

Testing async Hook functions

비동기 Hooks를 테스트하기 위해, 우리는 React Hooks Testing Library에서 제공하는 waitForNextUpdate 함수를 사용할 수 있어요. 비동기 메소드들은 프로미스를 반환하기 때문에, await.then을 사용해서 호출해야 해요.

우리가 테스트할 비동기 Hook은 API URL을 매개변수로 받아, Axios를 이용해 비동기 요청을 하고 응답 객체를 반환합니다.

src 폴더에 useFetchData.js 파일을 생성하고 다음 내용을 추가하세요:

import axios from "axios";
import React from "react";

const endpoint = "https://jsonplaceholder.typicode.com/posts/1";

export default function useFetchData() {
  const [data, setData] = React.useState({
    state: "LOADING",
    error: "",
    data: []
  });

  const fetchData = async () => {
    try {
      const result = await axios.get(endpoint);
      setData({
        state: "SUCCESS",
        error: "",
        data: result.data // 데이터에 직접 접근
      });
    } catch (err) {
      setData({
        data: [],
        state: "ERROR",
        error: err
      });
    }
  };

  React.useEffect(() => {
    fetchData();
  }, []);

  return data;
}

이제 우리의 비동기 Hook을 테스트해 봅시다. src 폴더에 useFetchData.test.js 파일을 생성하고 다음 내용을 추가하세요:

import { renderHook } from "@testing-library/react-hooks";
import useFetchData from "./useFetchData";
import axios from "axios";
import { act } from "react-dom/test-utils";

jest.mock("axios");

const useApiMockData = [
  { id: 1, name: "Leanne Graham" },
  { id: 2, name: "Ervin Howell" }
];

axios.get.mockResolvedValue({ data: useApiMockData });

describe("useFetchData Hook", () => {
  it("초기 상태와 성공 상태", async () => {
    const { result, waitForNextUpdate } = renderHook(() => useFetchData());

    await waitForNextUpdate();

    expect(result.current).toMatchObject({
      data: useApiMockData,
      error: "",
      state: "SUCCESS"
    });
  });

  it("에러 상태", async () => {
    const errorMessage = "Network Error";
    axios.get.mockImplementationOnce(() =>
      Promise.reject(new Error(errorMessage))
    );
    const { result, waitForNextUpdate } = renderHook(() => useFetchData());

    await waitForNextUpdate();

    expect(result.current).toMatchObject({
      data: [],
      error: errorMessage,
      state: "ERROR"
    });
  });
});

위의 코드 블록에서, 우리는 Axios 호출과 상태 업데이트를 모방하고 있어요. 비동기 요청의 결과로 상태 업데이트가 발생할 때 act() 메소드로 코드를 감싸는 것에 주목하세요. 이제 우리가 테스트를 실행하면, 모든 것이 아래 이미지에 표시된 것처럼 통과됩니다.

React

Common pitfalls and solutions when testing React Hooks

React Hooks를 테스트하는 것은 꽤 어려울 수 있어요. 이 부분에서는 여러분이 마주칠 수 있는 몇 가지 일반적인 문제들과 그 문제들을 해결하기 위한 방법들에 대해 다룰 거예요.

Testing Hooks with external dependencies

여러분의 React 애플리케이션에서 특정 작업, 패키지 또는 API에 의존하는 React Hook이 있을 수 있어요. 이러한 의존성들을 분리하고 테스트하는 것은 어려울 수 있습니다. 만약 이러한 의존성들이 언제든지 변경될 수 있는 외부 API라면, 그 행동을 모방하는 것은 더욱 어려워집니다.

이 문제에 대한 해결책은 여러분의 의존성들을 모의(Mock)하는 것입니다. 모의(Mocking)는 테스트를 실행할 때 실제 컴포넌트나 함수들을 시뮬레이션된 버전으로 대체하는 것을 말해요.

아래는 API 요청을 모의하는 방법의 예시입니다:

import axios from 'axios';
jest.mock('axios');

const mockedData = [
  {
    "id": 1,
    "name": "Jimmy",
    "isVerified": true,
  }, 
  {
    "id": 2,
    "name": "Tommy",
    "isVerified": false,
  }
];

describe('API 모의하기', () => {
  test('API에서 데이터를 가져와야 합니다', async () => {
    axios.get.mockResolvedValue({ data: mockedData });
  });
})

위 코드 블록에서, 우리는 앱에서 사용할 의존성을 가져왔습니다. 이 경우에는 axios입니다. 그 다음, Jest를 사용하여 의존성을 모의하고 예상 결과를 가짜로 만들었어요.

Testing Hooks that trigger side effects

함수가 실행될 때 범위 밖에서 발생하는 모든 행동을 부작용(Side effects)이라고 합니다. 부작용의 예로는 데이터 가져오기 또는 브라우저 DOM 조작이 있습니다. 부작용을 테스트하는 것은 대부분 비동기적인 성격을 가지고 있어서 테스트 결과가 예측 불가능할 수 있기 때문에 까다로울 수 있습니다.

이 문제에 대한 해결책은 React Testing Library에서 제공하는 waitFor 함수를 async/await와 함께 사용하여 비동기 작업이 완료될 때까지 기다린 후에 단언문(assertions)을 작성하는 것입니다:

import { waitFor } from '@testing-library/react';

test('부작용이 발생해야 합니다', async () => {
  // 부작용을 트리거합니다
  const resultPromise = someAsyncFunction();

  // 프로미스가 해결될 때까지 기다립니다
  await waitFor(() => {
    expect(resultPromise).resolves.toBe(/* 예상 값 */);
  });
});

위 코드 블록에서는 async/await를 사용하여 비동기 코드를 기다리기 위해 waitFor 함수를 사용하는 방법을 보여줍니다.

Testing custom Hooks

React에서 커스텀 Hooks를 테스트하는 것은 커스텀 Hooks가 컴포넌트와 다르기 때문에 어려울 수 있어요. React Testing Library의 render() 함수를 사용해 테스트하려고 할 때, React Hooks는 React 컴포넌트 외부에서 사용될 수 없기 때문에 오류가 발생합니다.

이 문제에 대한 해결책은 render() 함수 대신 renderHook() 함수를 사용하는 것입니다. renderHook() 함수는 아래 코드 블록에서 보여주는 것처럼 컴포넌트 외부에서 Hooks를 렌더링할 수 있는 환경을 제공합니다:

import { renderHook } from '@testing-library/react-hooks';
import useCustomHook from './useCustomHook';

test('무언가를 해야 합니다', () => {
  const { result } = renderHook(() => useCustomHook());

  // hook의 반환 값에 기반한 단언문
  expect(result.current.someValue).toBe(/* 예상 값 */);
});

위 코드에서, 우리는 renderHook() 함수와 커스텀 Hook을 가져왔습니다. 그런 다음, 우리는 오류 없이 성공적으로 테스트를 수행했습니다.

Comparing Enzyme, Jest, and React Testing Library

Enzyme, Jest, 그리고 React Testing Library를 성능 테스트, 모의(Mocking) 기능, 확장성 등 여러 측면에서 비교해 볼게요:

  1. 성능 테스트: Jest는 전체 애플리케이션에 대한 테스트 커버리지와 성능 테스트를 제공하며, 빠른 속도로 테스트를 실행할 수 있습니다. Enzyme과 React Testing Library도 효율적으로 컴포넌트를 테스트하지만, Jest와 결합하여 사용될 때 가장 강력합니다.
  2. 모의(Mocking) 기능: Jest는 강력한 모의 기능을 내장하고 있어, 함수, 모듈, 그리고 타이머 등 다양한 것들을 쉽게 모의할 수 있습니다. Enzyme과 React Testing Library는 Jest의 모의 기능과 함께 사용되어 컴포넌트와 Hooks의 행동을 시뮬레이션하는데 도움을 줍니다.
  3. 확장성: Jest는 다양한 플러그인과 확장 기능을 통해 확장성이 뛰어납니다. Enzyme은 주로 React 컴포넌트를 테스트하기 위해 설계되었으며, React Testing Library는 보다 선언적인 접근 방식을 제공하여 사용자의 관점에서 테스트를 작성할 수 있게 해줍니다.
  4. 유저 인터페이스 테스트: React Testing Library는 사용자가 컴포넌트와 상호작용하는 방식을 테스트하는 데 초점을 맞추고 있습니다. Enzyme은 DOM 조작과 컴포넌트의 생명주기 메서드를 테스트하는데 유용합니다.
  5. 커뮤니티와 지원: Jest와 React Testing Library는 Facebook에 의해 지원되며 큰 커뮤니티를 가지고 있습니다. Enzyme은 Airbnb에서 시작되었으며, 여전히 널리 사용되고 있지만 React Testing Library로 전환하는 추세가 있습니다.

각 도구는 고유한 장점을 가지고 있으며, 프로젝트의 요구 사항과 팀의 선호도에 따라 적절한 도구를 선택하는 것이 중요합니다.

기준EnzymeJestReact Testing Library
성능기능이 많아 성능에 영향을 줄 수 있으나, 일반적으로 성능이 좋음. 프로젝트 크기와 같은 특정 요인이 성능을 제한할 수 있음매우 뛰어난 성능렌더링 및 컴포넌트 쿼리에서 더 나은 성능을 제공하는 경량 설계로 알려짐
사용성컴포넌트 중심 테스팅 및 조작에 대한 풍부한 기능 제공종합적인 기능 세트를 제공하는 범용 테스팅 프레임워크단순성, 사용자 중심 테스팅 및 효율적인 컴포넌트 쿼리에 중점을 둠
스냅샷 테스팅enzyme-to-json 패키지를 통해 지원됨지원됨스냅샷 테스팅을 처리할 수 있으나 그 목적으로 설계되지 않음
모킹 기능강력한 모킹 기능 및 유틸리티 제공내장 모킹 기능 제공내장된 모킹 기능이 제한적임
업데이트 및 유지 관리적극적으로 관리됨적극적으로 관리됨적극적으로 관리됨
확장성대규모 애플리케이션에 대해 덜 확장 가능모든 크기의 애플리케이션에 잘 확장됨모든 크기의 애플리케이션에 잘 확장됨

Conclusion

이 글에서 우리는 React Hooks 사용 방법을 살펴보고 Jest, Enzyme, 그리고 React Testing Library를 사용하여 React Hooks와 React 컴포넌트에 대한 테스트를 작성하는 방법에 대해 논의했습니다. 또한, React 컴포넌트 테스트를 위한 이 세 가지 옵션을 비교하여 각 도구가 우리의 테스트 전략에 어떻게 맞을 수 있는지 더 잘 이해할 수 있었습니다.

추가적으로, React Hooks를 테스트할 때 마주칠 수 있는 몇 가지 도전과제와 이러한 도전과제를 다루기 위해 구현할 수 있는 솔루션들에 대해서도 다뤘습니다. 우리가 React Hooks 테스트를 탐색하기 위해 사용한 데모 프로젝트는 CodeSandbox에서 확인할 수 있습니다

답글 남기기