React Testing Library로 유닛테스트 해보기

Written on September 29, 2019

처음 개발을 배울 때 테스트 주도 개발이 대세라는 이야기를 들었을 때 ‘코드를 테스트 하기 위해 또 다른 코드를 작성해야 한다고?’ 하며 겁먹었던 기억이 난다 ㅎㅎ 그런데 공부를 하다보니 조금 더 깔끔한 코드, 효율적인 코드에 대해 고민하게 되고 이참에 테스트에 대해 공부해 보기로 했다.

특히나 내가 주로 사용하는 Javascript는 동적 타입언어인 데다가 코드 문제를 즉시 확인해주는 컴파일러가 없기 때문에 실행하기 전에 오류를 발견하기 어렵고, 발견 하더라도 버그 발견 시점이 코드 구현 시점에서 멀어질수록 원인을 찾기 어렵다. 💦

react-testing-library

많은 테스트 프레임워크나 라이브러리가 있지만 JestEnzyme를 많이 사용한다고 한다.하지만! React Testing Library를 사용해 보기로… 그 이유는 React Testing Library가 사용자가 이용하는 관점에서 테스트를 하는 Behavior Driven Test(행위 주도 테스트)를 작성하기에 적합하기 때문이다.

라이브러리 설치 및 설정

먼저 React Testing Libraryjest-dom을 설치한다. React Testing Library는 실제 브라우저 DOM을 기준으로 테스트를 진행하기 때문에 jest-dom을 설치하는 것이다.

npm install --save-dev @testing-library/react
npm install --save-dev @testing-library/jest-dom

그리고 src/setupTests.js 파일에 아래 두 가지를 추가한다. 첫 번째 라인은 각 테스트가 끝날 때 마다 jest-dom에 렌더링된 내용을 지우게 하는 것이다. 두 번째 라인은 jest-dom이 제공하는 테스팅 matcher들이 있는데, 이것을 jest test 러너가 불러올 수 있게 하는 것이다.

import "@testing-library/react/cleanup-after-each";
import "@testing-library/jest-dom/extend-expect";

테스트 코드 작성

React Testing Library 주요 API

React Testing Library는 jest-dom에 테스트 하고 싶은 컴포넌트를 render()해서 그 DOM에 그려진 내용으로 테스트 하는 것이다. 렌더된 특정 텍스트나, 라벨로 DOM을 서치하는 getByText, getByLabelText, 등의 쿼리가 있고, 컴포넌트에 data-testid를 부여해 그 id로 확인하는 getByTestId 등의 쿼리가 있다. (더 많은 쿼리 보기) 또한, 동적 컴포넌트의 테스트를 위해 jest-dom에 액션을 주는 fireEvent API도 있다.

정적 테스트 코트 작성

먼저, name을 입력할 수 있는 Form 컴포넌트를 만들고 렌더링된 정적인 화면을 테스트 해보자.

// src/Form.js
import React, { useState } from "react";

const Form = ({ onSubmit }) => {
  const [name, setName] = useState("");
  const handleSubmit = event => {
    onSubmit(name);
    event.preventDefault();
  };
  return (
    <div className="top">
      <form onSubmit={handleSubmit}>
        <label>
          Name:
          <input
            data-testid="nameInput"
            type="text"
            name="name"
            value={name}
            onChange={({ target: { value } }) => setName(value)}
          />
        </label>
        <input type="submit" value="Submit" disabled={!name} />
      </form>
    </div>
  );
};
export default Form;

Form 컴포넌트가 위와 같을 때, 뷰가 제대로 그려졌는지 확인하기 위해서 아래와 같은 테스트 코드를 작성할 수 있다.

// src/Form.test.js
import React from "react";
import { render } from "@testing-library/react";
import Form from "./Form";

describe("<Form />", () => {
  it("renders input", () => {
    const { getByLabelText, getByText, getByTestId } = render(<Form />);
    const name = getByLabelText("Name:");
    const input = getByTestId("nameInput");
    const button = getByText("Submit");
    expect(name).toBeInTheDocument();
    expect(input).toBeInTheDocument();
    expect(button).toBeInTheDocument();
  });
});

동적 테스트 코트 작성

이제 액션이 일어났을 때의 테스트를 작성해 보자. fireEvent로 액션을 실행할 수 있는데 fireEvent(node: HTMLElement, event: Event) 형태와 fireEvent[eventName](node: HTMLElement, eventProperties: Object) 형태로 사용할 수 있다.

아무것도 입력하지 않았을 때는 submit 버튼이 disabled였다가, name이 입력되면 enabled로 바뀌는 테스트 코드는 아래와 같이 작성할 수 있다.

// src/Form.test.js
...
import { render, fireEvent } from "@testing-library/react";
...

describe("<Form />", () => {
...
  it("enables button when name entered", () => {
    const { getByLabelText, getByText } = render(<Form />);
    const name = getByLabelText("Name:");
    const button = getByText("Submit");
    expect(button).toBeDisabled();
    fireEvent.change(name, { target: { value: "maruzzing" } });
    expect(button).toBeEnabled();
  });
});

그리고 submit을 눌렀을 때 onSubmit 함수가 실행되는 것을 확인하는 코드는 아래와 같이 작성할 수 있다.

// src/Form.test.js
...

describe("<Form />", () => {
...
  it("submits form when buttion is clicked", () => {
    const onSubmit = jest.fn();
    const { getByLabelText, getByText } = render(<Form onSubmit={onSubmit} />);
    const name = getByLabelText("Name:");
    const button = getByText("Submit");
    fireEvent.change(name, { target: { value: "maruzzing" } });
    fireEvent.click(button);
    expect(onSubmit).toHaveBeenCalledTimes(1);
  });
});

완전신기 !! 테스트에 초록불이 켜졌을 때의 희열이란 !!! 🤩 조금 더 복잡한 컴포넌트의 test도 해 봐야 겠다!!

👩🏻‍💻 배우는 것을 즐기는 프론트엔드 개발자 입니다
부족한 블로그에 방문해 주셔서 감사합니다 🙇🏻‍♀️

in the process of becoming the best version of myself