clean-react-tests

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Clean React Tests

简洁的React测试

React tests should describe what the user can observe and do. Prefer Testing Library queries that match accessible UI and user-event interactions that match real workflows.
React测试应描述用户可以观察和执行的操作。优先选择与可访问UI匹配的Testing Library查询,以及与真实工作流程匹配的user-event交互。

R10: Query Priority

R10:查询优先级

Prefer:
  1. getByRole
    with accessible name
  2. getByLabelText
  3. getByText
    for non-interactive copy
  4. getByTestId
    only when user-facing queries are not practical
tsx
// Bad - coupled to markup
expect(container.querySelector(".save-button")).toBeTruthy();

// Good - coupled to user-visible behavior
expect(screen.getByRole("button", { name: "Save" })).toBeEnabled();
优先使用:
  1. 带可访问名称的
    getByRole
  2. getByLabelText
  3. 用于非交互式文本内容的
    getByText
  4. 仅当面向用户的查询不可行时才使用
    getByTestId
tsx
// 不良示例 - 与标记耦合
expect(container.querySelector(".save-button")).toBeTruthy();

// 良好示例 - 与用户可见行为耦合
expect(screen.getByRole("button", { name: "Save" })).toBeEnabled();

Interactions

交互操作

Use
userEvent
for user workflows. Use
fireEvent
only for low-level events that user-event cannot express.
tsx
const user = userEvent.setup();

await user.type(screen.getByLabelText("Email"), "ada@example.com");
await user.click(screen.getByRole("button", { name: "Invite" }));

expect(await screen.findByText("Invitation sent")).toBeInTheDocument();
使用
userEvent
模拟用户工作流程。仅当user-event无法表达低级别事件时才使用
fireEvent
tsx
const user = userEvent.setup();

await user.type(screen.getByLabelText("Email"), "ada@example.com");
await user.click(screen.getByRole("button", { name: "Invite" }));

expect(await screen.findByText("Invitation sent")).toBeInTheDocument();

Behavior Contracts

行为契约

Assert what the user can observe or do: visible content, enabled or disabled controls, focus, selected state, expanded state, loading state, error messages, navigation, and other meaningful outcomes.
Do not test styling implementation details unless they are the public contract or the bug is specifically visual. Avoid assertions for CSS classes,
display: grid
vs
display: flex
, exact DOM nesting, spacing values, or layout primitives when the same user behavior remains intact.
tsx
// Bad - locks in an incidental layout implementation
expect(screen.getByTestId("actions")).toHaveStyle({ display: "grid" });

// Good - checks the behavior the layout supports
expect(screen.getByRole("button", { name: "Save" })).toBeVisible();
expect(screen.getByRole("button", { name: "Cancel" })).toBeVisible();
断言用户可以观察或执行的内容:可见内容、启用/禁用的控件、焦点、选中状态、展开状态、加载状态、错误消息、导航以及其他有意义的结果。
除非样式是公开契约或bug明确与视觉相关,否则不要测试样式实现细节。当用户行为保持不变时,避免对CSS类、
display: grid
display: flex
、精确DOM嵌套、间距值或布局原语进行断言。
tsx
// 不良示例 - 锁定了偶然的布局实现
expect(screen.getByTestId("actions")).toHaveStyle({ display: "grid" });

// 良好示例 - 检查布局支持的行为
expect(screen.getByRole("button", { name: "Save" })).toBeVisible();
expect(screen.getByRole("button", { name: "Cancel" })).toBeVisible();

Async UI

异步UI

  • Use
    findBy...
    for elements that appear asynchronously.
  • Use
    waitFor
    for assertions that need polling.
  • Avoid arbitrary sleeps and timers unless the component behavior is timer-based.
  • 对异步出现的元素使用
    findBy...
  • 对需要轮询的断言使用
    waitFor
  • 除非组件行为基于计时器,否则避免使用任意休眠和计时器。

Mocking

模拟

  • Mock network and browser boundaries, not the component under test.
  • Keep mocks close to the behavior being tested.
  • Keep mocked data realistic; use the Test Data guidance for large fixtures.
  • 模拟网络和浏览器边界,而不是被测组件。
  • 保持模拟与正在测试的行为紧密相关。
  • 保持模拟数据真实;对于大型测试夹具,请遵循测试数据指南。

R15: Test Data

R15:测试数据

Use builders for repeated props, query results, provider state, and domain objects with many required fields. A test should override the fields that matter to the behavior and inherit valid defaults for everything else.
tsx
// Bad - fixture noise hides the state being tested
render(<OrderSummary order={{ id: "1", status: "paid", lineItems: [], discounts: [] }} />);

// Good - the relevant state is the visible part
render(<OrderSummary order={buildOrder({ status: "paid" })} />);
Keep builders realistic and small. Inline JSX props are fine when every field matters or the object shape is tiny.
对于具有多个必填字段的重复props、查询结果、提供者状态和领域对象,使用构建器。测试应覆盖与行为相关的字段,并继承其他所有字段的有效默认值。
tsx
// 不良示例 - 测试夹具的冗余信息掩盖了要测试的状态
render(<OrderSummary order={{ id: "1", status: "paid", lineItems: [], discounts: [] }} />);

// 良好示例 - 相关状态是可见部分
render(<OrderSummary order={buildOrder({ status: "paid" })} />);
保持构建器真实且精简。当每个字段都重要或对象结构很小时,内联JSX props是可行的。

Coverage

覆盖率

Test:
  • Loading, empty, success, and error states
  • Disabled and pending interactions
  • Important accessibility states
  • Boundary cases around conditional rendering
测试以下内容:
  • 加载、空、成功和错误状态
  • 禁用和待处理的交互
  • 重要的可访问性状态
  • 条件渲染的边界情况

Common Mistakes

常见错误

  • Asserting implementation details like component state, hook calls, or CSS classes without user impact.
  • Locking tests to incidental style choices like grid vs flexbox, exact spacing, or DOM shape.
  • Using snapshots as the primary assertion for interactive UI.
  • Forgetting to await user interactions and async assertions.
  • Testing child components again in every parent test instead of checking integration behavior.
  • Building large inline props or query responses full of irrelevant fields.
  • 断言对用户无影响的实现细节,如组件状态、hook调用或CSS类。
  • 将测试锁定到偶然的样式选择,如grid与flexbox、精确间距或DOM结构。
  • 将快照作为交互式UI的主要断言方式。
  • 忘记等待用户交互和异步断言。
  • 在每个父组件测试中重复测试子组件,而不是检查集成行为。
  • 构建包含大量无关字段的大型内联props或查询响应。