Skip to content
alexgoni edited this page Jul 27, 2024 · 1 revision

Test!

1. Jest

현재 저희 프로젝트에서는 테스트 라이브러리로 Jest를 사용하고 있습니다. Jest의 기본 문법은 다음과 같습니다.

test("테스트에 대한 설명", () => {
  expect(검증할 ).toBe(기대값);
});

하나에 대해 여러 개의 테스트를 작성할 때는 describe로 묶을 수 있습니다. describe 안에서 test는 it으로 표기하기도 합니다.

describe("테스트 집합", () => {
  it(("test 1") => {
    // ...
  })

   it(("test 2") => {
    // ...
  })
})

add 함수에 대해 테스트를 작성해 보면 다음과 같겠네요!

// utils/add.ts

export default function add(a: number, b: number) {
  return a + b;
}
// __tests__/add.test.ts

import add from "/utils/add.ts";

describe("add 함수에 대한 테스트", () => {
  it("test 1", () => {
    const result = add(1, 2);

    expect(result).toBe(3);
  });

  it("test 2", () => {
    const result = add(2, 3);

    expect(result).toBe(5);
  });
});

위 예시에서는 toBe로 result와 기대하는 값을 비교했는데요. toBe와 같이 검증할 값과 기대하는 값을 비교하게 해주는 메서드를 Matcher라고 합니다.

jest에서는 다양한 Matcher를 제공하고 있는데요. 자주 쓰이는 Matcher는 다음과 같습니다.

원시값

  • toBe

객체

  • toEqual
  • toStrictEqual: 엄격하게 비교 (권장)

null, undefined

  • toBeNull
  • toBeUndefined
  • toBeDefined

truthy, falsy

  • toBeTrurhy
  • toBeFalsy

숫자 비교

  • toBeGreaterThan
  • toBeGreaterThanOrEqual
  • toBeLessThan
  • toBeLessThanOrEqual

2. Test in React

이제 리액트에서 테스트를 하는 방법을 정리해보겠습니다. 리액트에서 테스트는 다음과 같은 순서로 진행됩니다.

  1. 컴포넌트를 렌더링한다.
  2. 필요하다면 컴포넌트에서 특정 액션을 수행한다.
  3. 기대하는 결과와 실제 결과를 비교한다.

2번이 추가된 것을 제외하고는 일반 함수를 테스트하는 것과 같습니다. 특정 액션이 수행되지 않는 컴포넌트부터 테스트 해보겠습니다.

2.1 정적 컴포넌트

// components/TestComponent.tsx

export default function TestComponent() {
  return <h1>hello test!</h1>;
}
// __tests__/TestComponent.tsx

import TestComponent from "/components/TestComponent";
import { render, screen } from "@testing-library/react";

describe("TestComponent 테스트", () => {
  it("h1 테스트 with screen", () => {
    // 1. 컴포넌트를 렌더링한다.
    render(<TestComponent />);

    const headingElement = screen.getByRole("heading");

    // 3. 기대하는 결과와 실제 결과를 비교한다.
    expect(headingElement).toHaveTextContent(/hello test!/);
  });

  it("h1 테스트 with container", () => {
    const { container } = render(<TestComponent />);

    // render 함수가 반환하는 container를 통해 전통적인 방식으로 DOM에 접근할 수도 있습니다.
    const headingElement = container.querySelector("h1");

    expect(headingElement).toHaveTextContent(/hello test!/);
  });
});

이전과 마찬가지로 검증할 값과 기대값을 비교하는 것은 같습니다. 다만 검증할 값을 가져오는 방법이 다르고, 그에 따른 Matcher가 다릅니다. 각각 알아보겠습니다.

검증할 요소 가져오기

리액트 컴포넌트를 테스트하기 위해서는 HTML 요소를 가져와야 합니다. 이 방법은 크게 3가지가 있습니다.

  1. getBy...: 해당 요소가 없거나 두 개 이상이면 에러를 발생시킨다. 복수 개를 찾고 싶다면 getAllBy...를 사용하면 된다.
  2. findBy...: getBy...와 유사하나 비동기 액션 이후에 요소를 찾을 때 사용한다.
  3. queryBy...: 요소를 찾을 때 에러를 발생시키는 위 두 방법과 달리 null을 반환한다.

getBy... 예시: https://olaf-go.medium.com/testing-03-screen-getby-bb96787d2d4b


위의 모든 방법으로 Element를 찾을 수 없을 때는 데이터셋을 이용합니다.

<div data-testid="my-div" />
const myDivEl = screen.getByTestId("my-div");

그러나 이 방법은 테스트를 위해 기존 코드와 상관없는 속성이 HTML에 추가됩니다. 따라서 최후의 방법으로 사용해주세요.

Matcher

자주 사용하는 Matcher 입니다.

  • toHaveTextContent: 요소가 특정 텍스트를 포함하고 있는지 확인합니다.
  • toHaveClass: 요소가 특정 className을 가지고 있는지 확인합니다.
  • toBeInTheDocument: 요소가 DOM에 존재하는지 확인합니다.
  • toBeDisabled: 요소(버튼, 인풋 등)가 비활성화 상태인지 확인합니다.
  • toThrow: 함수가 예외를 발생시키는지 확인합니다.

그 외에 Matcher는 아래를 참고해주세요.

https://github.com/testing-library/jest-dom

2.2 동적 컴포넌트

이어서 사용자 상호작용이 동반된 동적 컴포넌트 테스트 예시를 살펴보겠습니다.

// __tests__/components/Pagination.test.tsx

import Pagination from "@/components/Pagination";
import { fireEvent, render, screen } from "@testing-library/react";

describe("Pagination Component", () => {
  it("prev 버튼 클릭 테스트", () => {
    // given
    render(<Pagination totalPages={10} initialPage={4} />);
    const prevButton = screen.getByRole("button", { name: /prev/i });

    // when
    fireEvent.click(prevButton);

    // then
    expect(screen.getByText("3")).toHaveClass("bg-custom-green-200");
  });
});

이전과 달라진 것이 있다면 fireEvent 메서드가 추가되었습니다.

페이지네이션 컴포넌트에서 fireEvent 메서드로 prev 버튼을 클릭하였습니다.

"bg-custom-green-200"은 활성화 된 페이지 번호를 의미하며, 현재 테스트 코드에서는 처음 페이지가 4번 페이지였으므로 3번 페이지가 활성화되기를 기대합니다.

테스트를 해보면 Pass가 되는 것을 확인할 수 있네요!

image


기타

  1. 현재 프로젝트에서는 pnpm test__tests__ 의 테스트 코드를 실행할 수 있습니다.

  2. pnpm test:coverage 명령어를 실행했을 때는 jest의 커버리지 리포트를 받을 수 있습니다.

스크린샷 2024-07-27 143417

작성한 테스트 코드들이 코드베이스를 얼마나 잘 커버하는지 보여줍니다. 가장 우측 열에서는 코드베이스에서 테스트되지 않은 코드 줄을 나타내는데요.

어떤 것을 테스트할지 감이 안 잡히실 때는 커버리지 리포트를 이용해보는 것도 좋은 방법인 것 같습니다.

간단한 생각 정리...

처음 테스트 코드를 접했을 때는 예시를 보면서 왜 당연한 걸 테스트할까라는 생각이 들었고, 쓸모 없는 것 같다고 여겨졌습니다.

테스트 코드가 존재하는 이유가 무엇일까요?

제가 생각했을 때 프론트엔드에서 테스트 코드를 작성함으로써 얻을 수 있는 이점은 다음과 같습니다.

  1. 어떤 것을 테스트할지 생각해보면서 요구 사항을 명확히 할 수 있습니다.
  2. 작성한 코드에 대해 믿음이 생깁니다.
  3. 결과물이 기존과 같아야 하는 리팩토링 시 이전의 결과를 직접 일일히 확인하지 않아도 됩니다.

다만 테스트 코드도 코드이고, 작성하는데 비용이 듭니다. 배보다 배꼽이 크면 안되겠죠!

그렇다면 어떤 것을 테스트해야 할까요? 해당 컴포넌트의 취약점이나 핵심 로직을 테스트 해야합니다.

어떤 것이 취약점이고 핵심 로직일까요? 이러한 생각들이 요구 사항을 명확히 하고, 고민들로 이루어진 테스트 코드가 컴포넌트를 더 단단하게 만들어주는 것 같습니다. (사실 지피티가 잘 작성해주더라구요 ㅎㅎ)

Jest, 리액트 테스트에 대해 설명이 너무 좋은 영상 공유드리면서 마치겠습니다 :)


Clone this wiki locally