testing by dalestudy/skills
npx skills add https://github.com/dalestudy/skills --skill testing基于 React Testing Library 的测试编写最佳实践及避免反模式指南。
Testing Library 的哲学:像用户一样进行测试
Testing Library 提供了多种查询,但应按照反映可访问性和用户体验的顺序使用。
getByRole (最高优先级) - 屏幕阅读器识别的方式getByLabelText - 表单元素(与 label 关联的 input)getByPlaceholderText - placeholder 明确的情况getByText - 通过文本内容搜索广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
getByDisplayValue - 通过当前输入的值搜索(表单元素)getByAltText - 图片 alt 属性getByTitle - title 属性(tooltip 等)getByTestId (最后手段) - 仅在其他方法不可行时使用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() 后使用await// ❌ 错误示例 - 使用 fireEvent
fireEvent.click(button);
fireEvent.change(input, { target: { value: "text" } });
// ✅ 正确示例 - 使用 userEvent
await user.click(button);
await user.type(input, "text");
// ✅ 正确示例 - 使用 findBy
const successMessage = await screen.findByText(/저장되었습니다/i);
expect(successMessage).toBeInTheDocument();
findBy = getBy + waitFor 组合(自动等待元素出现)
// 复杂的异步验证
await waitFor(() => {
expect(screen.getByRole("alert")).toHaveTextContent("성공");
});
// 多个条件验证
await waitFor(() => {
expect(mockFn).toHaveBeenCalledTimes(1);
expect(screen.queryByText(/로딩 중/i)).not.toBeInTheDocument();
});
// ❌ 错误示例 - 任意 timeout
await new Promise((resolve) => setTimeout(resolve, 1000));
// ❌ 错误示例 - 手动使用 act()(通常不需要)
await act(async () => {
// ...
});
// ✅ 正确示例 - 使用 findBy 或 waitFor
await screen.findByText(/완료/i);
// ❌ 错误示例 - 访问内部状态
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 }));
// ❌ 错误示例 - container.querySelector
const { container } = render(<MyComponent />);
const button = container.querySelector('.my-button');
// ✅ 正确示例 - screen 查询
const button = screen.getByRole('button', { name: /제출/i });
// ❌ 错误示例 - 对同步元素使用 waitFor
await waitFor(() => {
expect(screen.getByText("Hello")).toBeInTheDocument();
});
// ✅ 正确示例 - 同步元素立即验证
expect(screen.getByText("Hello")).toBeInTheDocument();
getBy* 如果找不到元素会抛出错误,因此从技术上讲 toBeInTheDocument() 是冗余的。但是不要删除它——它向代码读者传达的是这是经过重构后有意保留的存在性验证,而不是残留的查询。
// ❌ 错误示例 - 只保留查询而没有断言
screen.getByRole("button", { name: /제출/i });
// ✅ 正确示例 - 使用显式断言传达意图
expect(screen.getByRole("button", { name: /제출/i })).toBeInTheDocument();
但是,当验证的不是存在性而是其他属性时,不需要添加 toBeInTheDocument():
// ✅ 正确示例 - 其他匹配器已足够
expect(screen.getByRole("button", { name: /제출/i })).toBeDisabled();
// ❌ 错误示例 - 冗余断言
expect(screen.getByRole("button", { name: /제출/i })).toBeInTheDocument();
expect(screen.getByRole("button", { name: /제출/i })).toBeDisabled();
完整反模式列表:references/common-mistakes.md
需要测试环境设置时,请参考以下模板:
assets/vitest.config.ts - Vitest 设置(包含 React 插件、覆盖率)assets/test-setup.ts - Vitest 全局设置assets/msw-setup.ts - MSW 处理器及服务器设置Weekly Installs
129
Repository
GitHub Stars
4
First Seen
Jan 27, 2026
Security Audits
Installed on
claude-code125
opencode110
codex109
cursor109
github-copilot108
gemini-cli108
React Testing Library 기반 테스트 작성 모범 관례 및 안티패턴 회피 가이드.
Testing Library의 철학: 사용자가 사용하는 방식대로 테스트하라
Testing Library는 다양한 쿼리를 제공하지만, 접근성과 사용자 경험을 반영하는 순서 로 사용해야 함.
getByRole (최우선) - 스크린 리더가 인식하는 방식getByLabelText - 폼 요소 (label과 연결된 input)getByPlaceholderText - placeholder가 명확한 경우getByText - 텍스트 콘텐츠로 검색getByDisplayValue - 현재 입력된 값으로 검색 (폼 요소)getByAltText - 이미지 alt 속성getByTitle - title 속성 (tooltip 등)getByTestId (최후 수단) - 다른 방법이 불가능할 때만 사용상세 가이드 : references/query-priority.md
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() 호출 후 사용await 필수// ❌ 나쁜 예 - 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 사용
const successMessage = await screen.findByText(/저장되었습니다/i);
expect(successMessage).toBeInTheDocument();
findBy = getBy + waitFor 조합 (자동으로 요소 나타날 때까지 대기)
// 복잡한 비동기 검증
await waitFor(() => {
expect(screen.getByRole("alert")).toHaveTextContent("성공");
});
// 여러 조건 검증
await waitFor(() => {
expect(mockFn).toHaveBeenCalledTimes(1);
expect(screen.queryByText(/로딩 중/i)).not.toBeInTheDocument();
});
// ❌ 나쁜 예 - 임의의 timeout
await new Promise((resolve) => setTimeout(resolve, 1000));
// ❌ 나쁜 예 - act() 수동 사용 (보통 불필요)
await act(async () => {
// ...
});
// ✅ 좋은 예 - findBy 또는 waitFor
await screen.findByText(/완료/i);
상세 가이드 : references/async-patterns.md
// ❌ 나쁜 예 - 내부 상태 접근
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 }));
// ❌ 나쁜 예 - container.querySelector
const { container } = render(<MyComponent />);
const button = container.querySelector('.my-button');
// ✅ 좋은 예 - screen 쿼리
const button = screen.getByRole('button', { name: /제출/i });
// ❌ 나쁜 예 - 동기 요소에 waitFor
await waitFor(() => {
expect(screen.getByText("Hello")).toBeInTheDocument();
});
// ✅ 좋은 예 - 동기 요소는 즉시 검증
expect(screen.getByText("Hello")).toBeInTheDocument();
getBy*는 요소를 못 찾으면 throw하므로 toBeInTheDocument()는 기술적으로 중복이다. 하지만 제거하지 마라 — 리팩토링 후 남은 쿼리가 아니라 의도적인 존재 검증임을 코드 독자에게 전달하는 역할을 한다.
// ❌ 나쁜 예 - assertion 없이 쿼리만 남김
screen.getByRole("button", { name: /제출/i });
// ✅ 좋은 예 - 명시적 assertion으로 의도 전달
expect(screen.getByRole("button", { name: /제출/i })).toBeInTheDocument();
단, 존재 검증이 아닌 다른 속성을 검증할 때는 toBeInTheDocument()를 추가할 필요 없다:
// ✅ 좋은 예 - 다른 matcher로 충분
expect(screen.getByRole("button", { name: /제출/i })).toBeDisabled();
// ❌ 나쁜 예 - 중복 assertion
expect(screen.getByRole("button", { name: /제출/i })).toBeInTheDocument();
expect(screen.getByRole("button", { name: /제출/i })).toBeDisabled();
전체 안티패턴 목록 : references/common-mistakes.md
테스트 환경 설정이 필요한 경우 다음 템플릿 참조:
assets/vitest.config.ts - Vitest 설정 (React 플러그인, 커버리지 포함)assets/test-setup.ts - Vitest 글로벌 설정assets/msw-setup.ts - MSW 핸들러 및 서버 설정Weekly Installs
129
Repository
GitHub Stars
4
First Seen
Jan 27, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
claude-code125
opencode110
codex109
cursor109
github-copilot108
gemini-cli108
Vue 3 调试指南:解决响应式、计算属性与监听器常见错误
11,900 周安装
DNS管理指南:Route53、Azure DNS、CloudFlare配置与故障转移最佳实践
162 周安装
AWS MCP 服务器配置指南:为AI代理快速集成AWS工具与文档搜索
166 周安装
Docker平台指南:Windows/Linux/macOS差异、配置优化与最佳实践
70 周安装
Vision Framework API 参考:iOS/macOS 计算机视觉开发指南 (主体分割、OCR、姿态检测)
164 周安装
紫微斗数排盘与命理分析 - 专业星盘、十二宫、四化飞星运势推算
164 周安装
Synapse A2A 代理重新指令工具 - 恢复上下文重置后的代理身份与通信
166 周安装