testing

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Testing Library

Testing Library

React Testing Library 기반 테스트 작성 모범 관례 및 안티패턴 회피 가이드.
基于React Testing Library的测试编写最佳实践及避坑指南。

핵심 원칙

核心原则

Testing Library의 철학: 사용자가 사용하는 방식대로 테스트하라
  1. 접근성 기반 쿼리 우선 - 실제 사용자가 요소를 찾는 방식 사용
  2. 구현 세부사항 테스트 금지 - 컴포넌트 내부 상태/메서드 직접 접근 지양
  3. 실제 사용자 행동 시뮬레이션 - userEvent 사용, fireEvent 지양
  4. 비동기 처리 명시적 대기 - waitFor, findBy 활용
Testing Library的理念:按照用户的使用方式进行测试
  1. 优先使用基于可访问性的查询 - 采用实际用户查找元素的方式
  2. 禁止测试实现细节 - 避免直接访问组件内部状态/方法
  3. 模拟实际用户行为 - 使用userEvent,避免使用fireEvent
  4. 显式等待异步处理 - 利用waitFor、findBy

쿼리 우선순위

查询优先级

Testing Library는 다양한 쿼리를 제공하지만, 접근성과 사용자 경험을 반영하는 순서로 사용해야 함.
Testing Library提供多种查询方式,但需按照反映可访问性和用户体验的顺序使用。

권장 쿼리 순서 (높음 → 낮음)

推荐查询顺序(高→低)

  1. getByRole
    (최우선) - 스크린 리더가 인식하는 방식
  2. getByLabelText
    - 폼 요소 (label과 연결된 input)
  3. getByPlaceholderText
    - placeholder가 명확한 경우
  4. getByText
    - 텍스트 콘텐츠로 검색
  5. getByDisplayValue
    - 현재 입력된 값으로 검색 (폼 요소)
  6. getByAltText
    - 이미지 alt 속성
  7. getByTitle
    - title 속성 (tooltip 등)
  8. getByTestId
    (최후 수단) - 다른 방법이 불가능할 때만 사용
상세 가이드: references/query-priority.md
  1. getByRole
    (最高优先级)- 屏幕阅读器识别的方式
  2. getByLabelText
    - 表单元素(与label关联的input)
  3. getByPlaceholderText
    - 占位符明确的情况
  4. getByText
    - 通过文本内容搜索
  5. getByDisplayValue
    - 通过当前输入的值搜索(表单元素)
  6. getByAltText
    - 图片alt属性
  7. getByTitle
    - title属性(如提示框等)
  8. getByTestId
    (最后选择)- 仅在其他方法不可用时使用
详细指南references/query-priority.md

사용자 상호작용 테스트

用户交互测试

userEvent 사용 (권장)

使用userEvent(推荐)

typescript
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

test('사용자가 폼을 제출할 수 있다', async () => {
  const user = userEvent.setup();
  render(<LoginForm />);

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

  expect(await screen.findByText(/환영합니다/i)).toBeInTheDocument();
});
핵심:
  • userEvent.setup()
    호출 후 사용
  • 모든 user 메서드는
    await
    필수
  • 실제 브라우저 이벤트 순서 재현 (focus, keydown, keyup 등)
typescript
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

test('사용자가 폼을 제출할 수 있다', async () => {
  const user = userEvent.setup();
  render(<LoginForm />);

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

  expect(await screen.findByText(/환영합니다/i)).toBeInTheDocument();
});
核心:
  • 调用
    userEvent.setup()
    后使用
  • 所有user方法必须加
    await
  • 重现实际浏览器事件顺序(focus、keydown、keyup等)

fireEvent 지양

避免使用fireEvent

typescript
// ❌ 나쁜 예 - fireEvent 사용
fireEvent.click(button);
fireEvent.change(input, { target: { value: "text" } });

// ✅ 좋은 예 - userEvent 사용
await user.click(button);
await user.type(input, "text");
상세 가이드: references/user-events.md
typescript
// ❌ 나쁜 예 - fireEvent 사용
fireEvent.click(button);
fireEvent.change(input, { target: { value: "text" } });

// ✅ 좋은 예 - userEvent 사용
await user.click(button);
await user.type(input, "text");
详细指南references/user-events.md

비동기 처리

异步处理

findBy 쿼리 (권장)

使用findBy查询(推荐)

typescript
// ✅ 좋은 예 - findBy 사용
const successMessage = await screen.findByText(/저장되었습니다/i);
expect(successMessage).toBeInTheDocument();
findBy
=
getBy
+
waitFor
조합 (자동으로 요소 나타날 때까지 대기)
typescript
// ✅ 좋은 예 - findBy 사용
const successMessage = await screen.findByText(/저장되었습니다/i);
expect(successMessage).toBeInTheDocument();
findBy
=
getBy
+
waitFor
的组合(自动等待元素出现)

waitFor 사용

使用waitFor

typescript
// 복잡한 비동기 검증
await waitFor(() => {
  expect(screen.getByRole("alert")).toHaveTextContent("성공");
});

// 여러 조건 검증
await waitFor(() => {
  expect(mockFn).toHaveBeenCalledTimes(1);
  expect(screen.queryByText(/로딩 중/i)).not.toBeInTheDocument();
});
typescript
// 복잡한 비동기 검증
await waitFor(() => {
  expect(screen.getByRole("alert")).toHaveTextContent("성공");
});

// 여러 조건 검증
await waitFor(() => {
  expect(mockFn).toHaveBeenCalledTimes(1);
  expect(screen.queryByText(/로딩 중/i)).not.toBeInTheDocument();
});

안티패턴

反模式

typescript
// ❌ 나쁜 예 - 임의의 timeout
await new Promise((resolve) => setTimeout(resolve, 1000));

// ❌ 나쁜 예 - act() 수동 사용 (보통 불필요)
await act(async () => {
  // ...
});

// ✅ 좋은 예 - findBy 또는 waitFor
await screen.findByText(/완료/i);
상세 가이드: references/async-patterns.md
typescript
// ❌ 나쁜 예 - 임의의 timeout
await new Promise((resolve) => setTimeout(resolve, 1000));

// ❌ 나쁜 예 - act() 수동 사용 (보통 불필요)
await act(async () => {
  // ...
});

// ✅ 좋은 예 - findBy 또는 waitFor
await screen.findByText(/완료/i);
详细指南references/async-patterns.md

자주 하는 실수

常见错误

1. 구현 세부사항 테스트

1. 测试实现细节

typescript
// ❌ 나쁜 예 - 내부 상태 접근
expect(component.state.isOpen).toBe(true);
wrapper.find(".internal-class").simulate("click");

// ✅ 좋은 예 - 사용자 관점 검증
expect(screen.getByRole("dialog")).toBeVisible();
await user.click(screen.getByRole("button", { name: /열기/i }));
typescript
// ❌ 나쁜 예 - 내부 상태 접근
expect(component.state.isOpen).toBe(true);
wrapper.find(".internal-class").simulate("click");

// ✅ 좋은 예 - 사용자 관점 검증
expect(screen.getByRole("dialog")).toBeVisible();
await user.click(screen.getByRole("button", { name: /열기/i }));

2. container 쿼리 사용

2. 使用container查询

typescript
// ❌ 나쁜 예 - container.querySelector
const { container } = render(<MyComponent />);
const button = container.querySelector('.my-button');

// ✅ 좋은 예 - screen 쿼리
const button = screen.getByRole('button', { name: /제출/i });
typescript
// ❌ 나쁜 예 - container.querySelector
const { container } = render(<MyComponent />);
const button = container.querySelector('.my-button');

// ✅ 좋은 예 - screen 쿼리
const button = screen.getByRole('button', { name: /제출/i });

3. 불필요한 waitFor

3. 不必要的waitFor

typescript
// ❌ 나쁜 예 - 동기 요소에 waitFor
await waitFor(() => {
  expect(screen.getByText("Hello")).toBeInTheDocument();
});

// ✅ 좋은 예 - 동기 요소는 즉시 검증
expect(screen.getByText("Hello")).toBeInTheDocument();
전체 안티패턴 목록: references/common-mistakes.md
typescript
// ❌ 나쁜 예 - 동기 요소에 waitFor
await waitFor(() => {
  expect(screen.getByText("Hello")).toBeInTheDocument();
});

// ✅ 좋은 예 - 동기 요소는 즉시 검증
expect(screen.getByText("Hello")).toBeInTheDocument();
完整反模式列表references/common-mistakes.md

Vitest + MSW 설정

Vitest + MSW配置

테스트 환경 설정이 필요한 경우 다음 템플릿 참조:
  • assets/vitest.config.ts
    - Vitest 설정 (React 플러그인, 커버리지 포함)
  • assets/test-setup.ts
    - Vitest 글로벌 설정
  • assets/msw-setup.ts
    - MSW 핸들러 및 서버 설정
需要配置测试环境时,请参考以下模板:
  • assets/vitest.config.ts
    - Vitest配置(包含React插件、覆盖率)
  • assets/test-setup.ts
    - Vitest全局配置
  • assets/msw-setup.ts
    - MSW处理器及服务器配置