Jest와 React Testing Library


Jest는 Facebook에서 만든 JavaScript 테스트 프레임워크이고, React Testing Library는 사용자 관점에서 React 컴포넌트를 테스트하는 라이브러리입니다. 함께 사용하면 효과적인 프론트엔드 테스트를 작성할 수 있습니다.



언제 사용하나요?



  • React 컴포넌트 렌더링 및 동작 테스트

  • 사용자 인터랙션 시뮬레이션

  • API 호출 모킹

  • CI/CD 파이프라인에서 자동화된 테스트



설치


# Create React App에는 이미 포함됨
npm install --save-dev jest @testing-library/react @testing-library/jest-dom @testing-library/user-event


기본 테스트 구조


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

describe("Button 컴포넌트", () => {
it("버튼 텍스트가 렌더링된다", () => {
render(<Button>클릭</Button>);
expect(screen.getByText("클릭")).toBeInTheDocument();
});

it("클릭 시 onClick이 호출된다", async () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>클릭</Button>);

await userEvent.click(screen.getByRole("button"));
expect(handleClick).toHaveBeenCalledTimes(1);
});
});


쿼리 메서드


// getBy - 요소가 없으면 에러
screen.getByText("Hello");
screen.getByRole("button");
screen.getByLabelText("이메일");
screen.getByPlaceholderText("입력하세요");
screen.getByTestId("submit-btn");

// queryBy - 요소가 없으면 null (없음 확인용)
expect(screen.queryByText("에러")).not.toBeInTheDocument();

// findBy - 비동기, Promise 반환
const item = await screen.findByText("로딩 완료");

// 복수형 - 여러 요소
const buttons = screen.getAllByRole("button");


사용자 이벤트 테스트


import userEvent from "@testing-library/user-event";

it("폼 입력 및 제출 테스트", async () => {
const user = userEvent.setup();
const onSubmit = jest.fn();
render(<LoginForm onSubmit={onSubmit} />);

// 입력
await user.type(screen.getByLabelText("이메일"), "test@test.com");
await user.type(screen.getByLabelText("비밀번호"), "password123");

// 체크박스
await user.click(screen.getByLabelText("자동 로그인"));

// 선택 박스
await user.selectOptions(
screen.getByRole("combobox"),
["option1"]
);

// 제출
await user.click(screen.getByRole("button", { name: "로그인" }));

expect(onSubmit).toHaveBeenCalledWith({
email: "test@test.com",
password: "password123",
});
});


비동기 테스트


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

it("데이터 로딩 후 목록이 표시된다", async () => {
render(<UserList />);

// 로딩 상태 확인
expect(screen.getByText("로딩 중...")).toBeInTheDocument();

// 데이터 로드 대기
await waitFor(() => {
expect(screen.getByText("사용자1")).toBeInTheDocument();
});

// 또는 findBy 사용
const user = await screen.findByText("사용자1");
expect(user).toBeInTheDocument();
});


API 모킹


// api.js 모킹
jest.mock("./api");
import { fetchUsers } from "./api";

beforeEach(() => {
fetchUsers.mockResolvedValue([
{ id: 1, name: "홍길동" },
{ id: 2, name: "김철수" },
]);
});

it("API에서 사용자 목록을 가져온다", async () => {
render(<UserList />);

await screen.findByText("홍길동");
expect(fetchUsers).toHaveBeenCalledTimes(1);
});

// fetch 직접 모킹
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ data: "test" }),
})
);


커스텀 훅 테스트


import { renderHook, act } from "@testing-library/react";
import useCounter from "./useCounter";

it("useCounter 훅 테스트", () => {
const { result } = renderHook(() => useCounter(0));

expect(result.current.count).toBe(0);

act(() => {
result.current.increment();
});

expect(result.current.count).toBe(1);
});


스냅샷 테스트


it("컴포넌트 스냅샷 일치", () => {
const { container } = render(<Button>테스트</Button>);
expect(container).toMatchSnapshot();
});


주요 매처


// jest-dom 매처
expect(element).toBeInTheDocument();
expect(element).toBeVisible();
expect(element).toBeDisabled();
expect(element).toHaveClass("active");
expect(element).toHaveValue("test");
expect(element).toHaveAttribute("type", "submit");
expect(element).toHaveTextContent("Hello");




  • 구현 상세보다 사용자 행동 테스트

  • getByRole을 우선 사용 (접근성 향상)

  • act() 경고 시 waitFor 사용

  • 테스트 간 격리 (beforeEach로 초기화)