React Testing Library로 테스트 작성하기 – 초보자도 쉽게 따라하는 완벽 가이드

React Testing Library로 테스트 작성하기 – 초보자도 쉽게 따라하는 완벽 가이드

1. 도입 – 학습 목표 및 필요성

React Testing Library로 테스트 작성하기는 현대 프론트엔드 개발에서 필수적인 스킬입니다. 이 튜토리얼을 통해 컴포넌트의 동작을 검증하고, 사용자 관점에서 테스트를 작성하는 방법을 배우게 됩니다. React Testing Library는 실제 사용자가 애플리케이션과 상호작용하는 방식을 테스트하도록 설계되어, 구현 세부사항이 아닌 동작에 집중할 수 있게 해줍니다. 이를 통해 리팩토링에 강하고 유지보수가 쉬운 테스트 코드를 작성할 수 있으며, 버그를 조기에 발견하고 코드 품질을 향상시킬 수 있습니다. 프로젝트의 안정성을 높이고 개발자로서의 역량을 한 단계 끌어올릴 준비가 되셨나요?

2. 기본 개념 설명

React Testing Library는 Kent C. Dodds가 만든 테스팅 라이브러리로, DOM Testing Library를 기반으로 합니다. 핵심 철학은 “테스트가 소프트웨어 사용 방식과 유사할수록 더 신뢰할 수 있다”는 것입니다. 전통적인 Enzyme과 달리 컴포넌트의 내부 state나 props에 직접 접근하지 않고, 렌더링된 DOM을 통해 테스트합니다.

주요 개념으로는 쿼리(Queries), 이벤트(Events), 비동기 처리(Async Utilities)가 있습니다. 쿼리는 getBy, queryBy, findBy 등으로 DOM 요소를 찾고, fireEvent나 userEvent로 사용자 상호작용을 시뮬레이션하며, waitFor, findBy 등으로 비동기 동작을 처리합니다. 또한 Jest와 함께 사용되어 expect 매처를 통해 검증을 수행합니다. 이러한 도구들은 실제 사용자 경험과 동일한 방식으로 컴포넌트를 테스트할 수 있게 하며, 접근성(Accessibility)도 자연스럽게 개선됩니다.

3. 단계별 구현 가이드

3-1. 환경 설정

먼저 Create React App으로 프로젝트를 생성하면 React Testing Library가 기본으로 포함되어 있습니다. 기존 프로젝트라면 다음 명령어로 설치합니다:

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

setupTests.js 파일에 jest-dom을 import하여 추가 매처를 사용할 수 있습니다:

import '@testing-library/jest-dom';

3-2. 첫 번째 테스트 작성

간단한 버튼 컴포넌트를 테스트해봅시다. 테스트 파일은 컴포넌트명.test.js 또는 컴포넌트명.spec.js로 작성합니다. 테스트의 기본 구조는 Arrange(준비), Act(실행), Assert(검증)의 AAA 패턴을 따릅니다.

먼저 render 함수로 컴포넌트를 렌더링하고, screen 객체를 통해 DOM 요소를 쿼리합니다. getByRole, getByText, getByLabelText 등 의미론적 쿼리를 우선 사용하는 것이 권장됩니다. 이는 접근성을 고려한 설계를 유도합니다.

3-3. 쿼리 우선순위 이해하기

쿼리 선택의 우선순위는 다음과 같습니다:

  1. getByRole: 가장 권장되는 방법으로 ARIA role을 기반으로 검색
  2. getByLabelText: 폼 요소에 적합
  3. getByPlaceholderText: placeholder로 검색
  4. getByText: 텍스트 콘텐츠로 검색
  5. getByDisplayValue: 폼 요소의 현재 값으로 검색
  6. getByAltText: 이미지의 alt 텍스트로 검색
  7. getByTitle: title 속성으로 검색
  8. getByTestId: 최후의 수단 (data-testid 사용)

3-4. 사용자 이벤트 처리

@testing-library/user-event는 fireEvent보다 실제 사용자 동작에 가깝습니다. 클릭, 타이핑, 선택 등의 상호작용을 더 현실적으로 시뮬레이션합니다. userEvent.setup()을 사용하여 인스턴스를 생성하고 async/await으로 이벤트를 처리하는 것이 최신 권장사항입니다.

3-5. 비동기 테스트

API 호출이나 지연된 렌더링을 테스트할 때는 findBy 쿼리나 waitFor 유틸리티를 사용합니다. findBy는 Promise를 반환하여 요소가 나타날 때까지 기다립니다. waitFor는 특정 조건이 충족될 때까지 대기하며, 기본 타임아웃은 1000ms입니다.

4. 실제 코드 예제와 설명

예제 1: 카운터 컴포넌트 테스트

// Counter.jsx
import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
      <button onClick={() => setCount(0)}>
        Reset
      </button>
    </div>
  );
}
// Counter.test.jsx
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Counter from './Counter';

describe('Counter Component', () => {
  test('초기 카운트가 0으로 렌더링된다', () => {
    render(<Counter />);
    expect(screen.getByRole('heading')).toHaveTextContent('Count: 0');
  });

  test('증가 버튼 클릭 시 카운트가 증가한다', async () => {
    const user = userEvent.setup();
    render(<Counter />);
    
    const incrementBtn = screen.getByRole('button', { name: /increment/i });
    await user.click(incrementBtn);
    
    expect(screen.getByRole('heading')).toHaveTextContent('Count: 1');
  });

  test('리셋 버튼 클릭 시 카운트가 0으로 초기화된다', async () => {
    const user = userEvent.setup();
    render(<Counter />);
    
    const incrementBtn = screen.getByRole('button', { name: /increment/i });
    const resetBtn = screen.getByRole('button', { name: /reset/i });
    
    await user.click(incrementBtn);
    await user.click(incrementBtn);
    await user.click(resetBtn);
    
    expect(screen.getByRole('heading')).toHaveTextContent('Count: 0');
  });
});

예제 2: 폼 제출 테스트

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

test('폼 제출 시 올바른 데이터가 전달된다', async () => {
  const user = userEvent.setup();
  const handleSubmit = jest.fn();
  
  render(<LoginForm onSubmit={handleSubmit} />);
  
  const emailInput = screen.getByLabelText(/email/i);
  const passwordInput = screen.getByLabelText(/password/i);
  const submitBtn = screen.getByRole('button', { name: /submit/i });
  
  await user.type(emailInput, '[email protected]');
  await user.type(passwordInput, 'password123');
  await user.click(submitBtn);
  
  expect(handleSubmit).toHaveBeenCalledWith({
    email: '[email protected]',
    password: 'password123'
  });
});

5. 고급 활용 방법

5-1. Custom Render 함수

Redux, Router, Theme Provider 등을 포함하는 커스텀 render 함수를 만들어 재사용성을 높일 수 있습니다:

// test-utils.jsx
import { render } from '@testing-library/react';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
import { store } from './store';

export function renderWithProviders(ui, options) {
  return render(
    <Provider store={store}>
      <BrowserRouter>
        {ui}
      </BrowserRouter>
    </Provider>,
    options
  );
}

5-2. MSW로 API 모킹

Mock Service Worker를 사용하면 네트워크 레벨에서 API를 모킹할 수 있습니다:

import { rest } from 'msw';
import { setupServer } from 'msw/node';

const server = setupServer(
  rest.get('/api/users', (req, res, ctx) => {
    return res(ctx.json([{ id: 1, name: 'John' }]));
  })
);

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

5-3. 접근성 테스트

jest-axe를 활용하여 접근성 문제를 자동으로 감지할 수 있습니다:

import { axe, toHaveNoViolations } from 'jest-axe';

expect.extend(toHaveNoViolations);

test('접근성 위반이 없어야 한다', async () => {
  const { container } = render(<MyComponent />);
  const results = await axe(container);
  expect(results).toHaveNoViolations();
});

6. 마무리 및 추가 학습 자료

React Testing Library로 테스트 작성하기를 통해 사용자 중심의 견고한 테스트를 구축하는 방법을 배웠습니다. 핵심은 구현이 아닌 동작을 테스트하고, 실제 사용자 경험을 반영하는 것입니다. 지속적인 연습을 통해 테스트 주도 개발(TDD)을 일상화하고, 코드 커버리지를 점진적으로 높여가세요.

추가 학습 자료:

  • 공식 문서: testing-library.com/react
  • Kent C. Dodds의 Testing JavaScript 코스
  • Common mistakes with React Testing Library 가이드
  • Testing Playground: 최적의 쿼리를 찾아주는 도구

이제 여러분의 프로젝트에 테스트를 적용하며 더 안정적이고 유지보수 가능한 코드를 작성해보세요!

📚 함께 읽으면 좋은 글

1

React 성능 최적화 완벽 가이드 – 초보자도 쉽게 따라하는 완벽 가이드

📂 React 튜토리얼
📅 2025. 11. 18.
🎯 React 성능 최적화 완벽 가이드

2

React Router 실전 사용법 – 초보자도 쉽게 따라하는 완벽 가이드

📂 React 튜토리얼
📅 2025. 11. 17.
🎯 React Router 실전 사용법

3

React 컴포넌트 설계 패턴 – 초보자도 쉽게 따라하는 완벽 가이드

📂 React 튜토리얼
📅 2025. 11. 17.
🎯 React 컴포넌트 설계 패턴

4

React 컴포넌트 설계 패턴 – 초보자도 쉽게 따라하는 완벽 가이드

📂 React 튜토리얼
📅 2025. 11. 17.
🎯 React 컴포넌트 설계 패턴

5

React 컴포넌트 설계 패턴 – 초보자도 쉽게 따라하는 완벽 가이드

📂 React 튜토리얼
📅 2025. 11. 16.
🎯 React 컴포넌트 설계 패턴

💡 위 글들을 통해 더 깊이 있는 정보를 얻어보세요!

📢 이 글이 도움되셨나요? 공유해주세요!

여러분의 공유 한 번이 더 많은 사람들에게 도움이 됩니다 ✨

🔥 공유할 때마다 블로그 성장에 큰 힘이 됩니다! 감사합니다 🙏

💬 여러분의 소중한 의견을 들려주세요!

이 글에서 가장 도움이 된 부분은 어떤 것인가요?

💡
유용한 정보 공유

궁금한 점 질문

🤝
경험담 나누기

👍
의견 표현하기

⭐ 모든 댓글은 24시간 내에 답변드리며, 여러분의 의견이 다른 독자들에게 큰 도움이 됩니다!
🎯 건설적인 의견과 경험 공유를 환영합니다 ✨

🔔 블로그 구독하고 최신 글을 받아보세요!

📚
다양한 주제
17개 카테고리

정기 업데이트
하루 3회 발행

🎯
실용적 정보
바로 적용 가능

💡
최신 트렌드
2025년 기준

🌟 React 튜토리얼부터 다양한 실생활 정보까지!
매일 새로운 유용한 콘텐츠를 만나보세요 ✨

📧 RSS 구독 | 🔖 북마크 추가 | 📱 모바일 앱 알림 설정
지금 구독하고 놓치는 정보 없이 업데이트 받아보세요!

답글 남기기