모든 강의를 무료로 볼 수 있어요. 회원가입 없이도 학습 가능합니다.

26강 / 전체 30강

에러 처리와 경계

5분 읽기 조회 5

React Testing Library와 Vitest로 컴포넌트를 테스트하는 방법, 좋은 테스트의 기준, 사용자 관점 테스트 철학을 실전 코드로 배웁니다.

🧪 왜 컴포넌트 테스트가 필요한가

"테스트 없이도 앱이 잘 돌아가는데 왜 시간 들여 테스트를 작성해야 하나?" 이 질문의 답은 리팩토링 자신감에 있습니다. 코드를 변경할 때마다 손으로 모든 기능을 확인하는 건 비효율적이고 빠뜨리기 쉽습니다. 테스트가 있으면 실수를 즉시 발견할 수 있습니다.

리액트 컴포넌트 테스트의 핵심 철학: "사용자처럼 테스트하라." DOM 내부 구현보다 사용자가 보고 클릭하는 것을 테스트합니다.

📦 설치 및 설정

npm install -D vitest @testing-library/react @testing-library/jest-dom @testing-library/user-event jsdom
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: './src/test/setup.ts',
  },
})
// src/test/setup.ts
import '@testing-library/jest-dom'

✅ 첫 번째 테스트 — Counter 컴포넌트

// Counter.tsx
import { useState } from 'react'

function Counter({ initialCount = 0 }: { initialCount?: number }) {
  const [count, setCount] = useState(initialCount)
  return (
    <div>
      <p data-testid="count">현재 값: {count}</p>
      <button onClick={() => setCount((c) => c + 1)}>증가</button>
      <button onClick={() => setCount((c) => c - 1)}>감소</button>
      <button onClick={() => setCount(0)}>초기화</button>
    </div>
  )
}
// Counter.test.tsx
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import Counter from './Counter'

describe('Counter', () => {
  it('초기값이 0으로 렌더링된다', () => {
    render(<Counter />)
    expect(screen.getByTestId('count')).toHaveTextContent('현재 값: 0')
  })

  it('initialCount prop을 받아 그 값으로 시작한다', () => {
    render(<Counter initialCount={10} />)
    expect(screen.getByTestId('count')).toHaveTextContent('현재 값: 10')
  })

  it('증가 버튼 클릭 시 값이 1 올라간다', async () => {
    const user = userEvent.setup()
    render(<Counter />)
    await user.click(screen.getByRole('button', { name: '증가' }))
    expect(screen.getByTestId('count')).toHaveTextContent('현재 값: 1')
  })

  it('초기화 버튼 클릭 시 0으로 돌아간다', async () => {
    const user = userEvent.setup()
    render(<Counter initialCount={5} />)
    await user.click(screen.getByRole('button', { name: '증가' }))
    await user.click(screen.getByRole('button', { name: '초기화' }))
    expect(screen.getByTestId('count')).toHaveTextContent('현재 값: 0')
  })
})

🔎 쿼리 우선순위 — 어떻게 요소를 찾을까

React Testing Library는 사용자가 요소를 찾는 방식을 모방합니다:

쿼리예시사용 시점
getByRolegetByRole('button', {name: '로그인'})접근성 역할로 — 가장 권장
getByLabelTextgetByLabelText('이메일')폼 입력 — label과 연결
getByPlaceholderTextgetByPlaceholderText('검색...')placeholder로 찾기
getByTextgetByText('제출')텍스트 내용으로
getByTestIdgetByTestId('count')최후 수단 — 구현에 의존적

📝 폼 테스트

it('로그인 폼을 올바르게 제출한다', async () => {
  const handleSubmit = vi.fn()
  const user = userEvent.setup()
  render(<LoginForm onSubmit={handleSubmit} />)

  await user.type(screen.getByLabelText('이메일'), 'test@example.com')
  await user.type(screen.getByLabelText('비밀번호'), 'password123')
  await user.click(screen.getByRole('button', { name: '로그인' }))

  expect(handleSubmit).toHaveBeenCalledWith({
    email: 'test@example.com',
    password: 'password123',
  })
})

✅ 정리

  • React Testing Library + Vitest: 컴포넌트를 사용자 관점에서 테스트합니다.
  • 요소 찾기: getByRole > getByLabelText > getByText > getByTestId 순서로 우선합니다.
  • userEvent: 실제 사용자 동작(타이핑, 클릭)을 시뮬레이션합니다.
  • 구현이 아닌 동작(behavior)을 테스트하세요 — 내부 state가 아닌 화면에 무엇이 보이는지.

관련 주제

  • 에러 바운더리 개념
  • 폼 검증
  • 사용자 친화적 에러 표시
  • 개발·프로그래밍
  • 개발·프로그래밍 강의
  • 리액트 입문 — 컴포넌트로 만드는 웹
  • 무료강의
  • 무료 온라인 강의
  • NUGUNA
  • 누구나

📚 시리즈 전체 공유

리액트 입문 — 컴포넌트로 만드는 웹

이 강의가 속한 시리즈는 총 30강, 모두 무료입니다. 처음부터 배우려는 동료에게 시리즈 전체를 알려 주세요.

댓글

0/1000

불러오는 중...