Logo

React Hooks Testing Library 사용법

React v16.8에서 리액트 훅(React Hook)이 소개된 이후로 많은 프로젝트에서 커스텀(custom) 훅 함수를 사용하고 있는 것 같은데요. 이번 포스팅에는 리액트 훅(React Hook)을 테스트하는 방법에 대해서 알아보겠습니다.

예제 React Hook

먼저 테스트 대상이 될 간단한 React Hook 함수 하나를 작성해보겠습니다.

아래 useToggle() 함수는 true 또는 false가 될 수 있는 상태 state와 그 상태값을 반전할 수 있는 함수 toggle()을 배열에 담아 반환합니다. 초기 상태값을 initialState 인자를 통해 받을 수 있으며, 인자를 넘기지 않은 경우 기본값으로 false를 사용하게 됩니다.

useToggle.js
import { useState, useCallback } from "react";

const useToggle = (initialState = false) => {
  const [state, setState] = useState(initialState);
  const toggle = useCallback(() => setState((state) => !state), []);
  return [state, toggle];
};

export default useToggle;

React Component를 통한 간접 테스트

일반적으로 React Hook은 해당 프로젝트 내의 다른 React Component에 의해서 쓰여지기 마련입니다. 따라서 해당 React Hook을 사용하는 React Component를 테스트 함으로써 간접적으로 React Hook을 테스트할 수 있습니다.

예를 들어, useToggle() 훅 함수를 사용하는 <ToggleButton/> 이라는 컴포넌트를 작성해보겠습니다.

ToggleButton.jsx
import useToggle from "./useToggle";

function ToggleButton({ initial = false }) {
  const [on, toggle] = useToggle(initial);

  return <button onClick={toggle}>{on ? "ON" : "OFF"}</button>;
}

export default ToggleButton;

그 다음, 일반적인 컴포넌트를 테스트하듯이 React Testing Library를 이용해서 테스트 코드를 작성할 수 있습니다.

React Testing Library에 대한 자세한 내용은 관련 포스팅를 참고 바랍니다.

ToggleButton.test.jsx
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";

import ToggleButton from "./ToggleButton";

test("button text changes from ON to OFF when clicked", () => {
  render(<ToggleButton />);

  const button = screen.getByRole("button");

  expect(button).toHaveAccessibleName("OFF");

  await userEvent.click(button);

  expect(button).toHaveAccessibleName("ON");
});

test("button text is ON given initial set to true", () => {
  render(<ToggleButton initial={true} />);

  expect(screen.getByRole("button", { name: /on/i })).toBeInTheDocument();
});

첫 번째 테스트에서는 버튼을 클릭하면 버튼 내부 문구가 OFF에서 ON으로 변경되는지를 검증하고, 두 번째 테스트에서는 컴포넌트에 initial prop으로 true를 넘겼을 때 내부 문구가 ON인 버튼이 랜더링되는지 검증하고 있습니다.

React Hooks Testing Library를 통한 직접 테스트

React Hook 함수 여러 개를 별도의 프로젝트로 관리하는 경우, 위와 같이 다른 React Component을 통한 간접 테스트 전략은 적합하지 않습니다. 오직 테스팅 목적으로 실제로 사용하지 않는 불필요한 React Component를 작성해야하기 때문입니다.

React Hooks Testing Library라는 좀 긴 이름을 가진 React Hook을 테스트 하기 위한 전용 라이브러리가 있습니다. 이 라이브러리를 이용하면 다른 React Component의 도움 없이도 React Hook을 직접 테스트를 할 수가 있습니다.

React Hooks Testing Library는 npm을 통해서 개발 의존성으로 설치할 수 있습니다.

$ npm i -D @testing-library/react-hooks

업데이트: React v18 부터 @testing-library/react-hooks 패키지는 @testing-library/react 패키지로 흡수되었습니다. 따라서 별도의 패키지 설치가 필요없이 @testing-library/react만 설치되어 있으면 됩니다.

React Hooks Testing Library의 renderHook() 함수에 React Hook 함수를 호출하는 코드를 인자로 넘기면 result 속성을 담고 있는 객체를 반환합니다. 이 result 객체는 current 속성을 갖는데, 이 속성을 통해서 해당 React Hook 함수의 반환값에 직접 접근할 수 있습니다.

예를 들어, 위에서 작성한 useToggle() 훅 함수는 state 상테와 toggle 함수로 이루어진 배열을 리턴합니다. 따라서 useToggle() 훅 함수를 인자로 renderHook() 함수를 호출하면 다음과 같이 result.current를 통해 state 상테와 toggle 함수에 접근할 수 있습니다.

const { result } = renderHook(() => useToggle());
console.log(result.current[0]); // state 상태 출력
console.log(result.current[1]); // toggle 함수 출력

이제 간단히 React Hooks Testing Library를 통해서 위에서 작성했던 테스트 코드를 재작성 해보겠습니다.

useToggle.test.js
import { renderHook, act } from "@testing-library/react-hooks"; // React 18 이전
import { renderHook, act } from '@testing-library/react'; // React v18 이후

import useToggle from "./useToggle";

test("update state from false to true when toggle is called", () => {
  const { result } = renderHook(() => useToggle());

  expect(result.current[0]).toBe(false);

  act(() => result.current[1]());

  expect(result.current[0]).toBe(true);
});

test("allows for initial value", () => {
  const { result } = renderHook(() => useToggle(true));

  expect(result.current[0]).toBe(true);
});

첫 번째 테스트에서는 toggle 함수가 호출하면 state 상태가 false에서 true로 변경되는지를 검증하고 있습니다. 두 번째 테스트에서는 훅 함수를 호출할 때 인자로 true를 넘기면 상태가 true로 시작하는지를 검증하고 있습니다.

여기서 눈 여겨 볼 점은 result.current[1]() 즉, toggle() 함수를 호출부를 act() 함수로 감싸줬다는 것인데요. toggle() 함수를 호출하면 state 상태가 바뀌기 때문에 act() 함수를 통해 수동으로 DOM을 업데이트 해주는 것입니다. 쉽게 얘기해서 React에게 상태가 업데이트되었으니 다시 랜더링을 하라고 지시해주는 것이 act() 함수의 역할입니다.

전체 코드

본 포스팅에서 Jest로 작성한 테스트 코드는 아래에서 확인하고 직접 실행해보실 수 있습니다.

Vitest를 사용하여 작성한 테스트 코드도 아래 올려두었으니 참고 바랍니다.

마치면서

이상으로 React Hook을 테스트하는 두가지 방법에 대해서 알아보았습니다. React Hooks Testing Library에 대한 좀 더 자세한 내용은 아래 관련 사이트를 참고 바랍니다.