React Testing Library

itechmeat/llm-code/skills/react-testing-library

作者 itechmeat7ae8a005245770a0fa1e10337b2eb10174c5886b無授權條款收錄於 2026年10月9日更新於 2026年10月9日

React Testing Library: user-centric component testing with queries, user-event simulation, async utilities, and accessibility-first API. Use when writing React component tests, selecting elements by role/label/text, simulating user events, or testing async UI behavior. Keywords: React Testing Library, @testing-library/react, user-event, queries, render.

AI 產生的概覽

使用 React Testing Library 撰寫 React 元件測試的參考指南,涵蓋查詢、使用者事件、非同步工具與設定。

功能
此技能提供使用 React Testing Library 測試 React 元件的說明與參考資料。它說明查詢方式與優先順序、user-event 事件模擬、waitFor 與 findBy 等非同步輔助方法、Hook 測試、自訂 render 包裝、除錯輸出、jest-dom 匹配器以及全域設定。它也列出反模式與最佳實務,並指向查詢、使用者事件、API、非同步、除錯與設定這六份參考文件。
適用情境
適用於撰寫或審查 React 元件測試,尤其是在依角色、標籤或文字選取元素、模擬使用者互動或測試非同步 UI 行為時。也適用於建立自訂 render 包裝或 renderHook 等測試工具。
執行需求
僅為說明文件,不附帶指令碼。依指南操作需要 JavaScript/TypeScript 專案、React,以及已安裝的 @testing-library/react 與 @testing-library/dom 套件,可選安裝 @testing-library/user-event 與 @testing-library/jest-dom,並需要 Jest 等測試執行器。

React Testing Library Skill

Quick Navigation

TopicLink
Queriesreferences/queries.md [blocked]
User Eventsreferences/user-events.md [blocked]
APIreferences/api.md [blocked]
Asyncreferences/async.md [blocked]
Debuggingreferences/debugging.md [blocked]
Configreferences/config.md [blocked]

Installation

Install: npm install --save-dev @testing-library/react @testing-library/dom. Recommended extras: @testing-library/user-event and @testing-library/jest-dom. React 19 requires v16.1.0+.

Core Philosophy

"The more your tests resemble the way your software is used, the more confidence they can give you."

Avoid testing:

  • Internal state of components
  • Internal methods
  • Lifecycle methods
  • Child component implementation details

Test instead:

  • What users see and interact with
  • Behavior from user's perspective
  • Accessibility (queries by role, label)

Query Priority

Use queries in this order of preference:

1. Accessible to Everyone (Preferred)

ts
// Best — by ARIA rolegetByRole("button", { name: /submit/i });getByRole("textbox", { name: /email/i });
// Form fields — by labelgetByLabelText("Email");
// Non-interactive content — by textgetByText("Welcome back!");

2. Semantic Queries

ts
// ImagesgetByAltText("Company logo");
// Title attribute (less reliable)getByTitle("Close");

3. Test IDs (Escape Hatch)

ts
// Only when other queries don't workgetByTestId("custom-element");

Query Types

TypeNo Match1 Match>1 MatchAsync
getBy...throwreturnthrowNo
queryBy...nullreturnthrowNo
findBy...throwreturnthrowYes
getAllBy...throwarrayarrayNo
queryAllBy...[]arrayarrayNo
findAllBy...throwarrayarrayYes

When to use:

  • getBy* — element exists
  • queryBy* — element may not exist (assertions like expect(...).not.toBeInTheDocument())
  • findBy* — element appears asynchronously

Basic Test Pattern

tsx
import { render, screen } from "@testing-library/react";import userEvent from "@testing-library/user-event";
test("shows greeting after login", async () => {  const user = userEvent.setup();  render(<App />);
  // Act — simulate user interactions  await user.type(screen.getByLabelText(/username/i), "john");  await user.click(screen.getByRole("button", { name: /login/i }));
  // Assert — verify outcome  expect(await screen.findByText(/welcome, john/i)).toBeInTheDocument();});

User Events

Always use @testing-library/user-event over fireEvent:

ts
import userEvent from "@testing-library/user-event";
test("user interactions", async () => {  const user = userEvent.setup();
  // Click  await user.click(element);  await user.dblClick(element);  await user.tripleClick(element);
  // Type  await user.type(input, "Hello");  await user.clear(input);
  // Select  await user.selectOptions(select, ["option1", "option2"]);
  // Keyboard  await user.keyboard("{Enter}");  await user.keyboard("[ShiftLeft>]a[/ShiftLeft]"); // Shift+A
  // Clipboard  await user.copy();  await user.paste();
  // Pointer  await user.hover(element);  await user.unhover(element);});

Async Patterns

waitFor — Retry Until Success

ts
await waitFor(() => {  expect(screen.getByText("Loaded")).toBeInTheDocument();});
// With optionsawait waitFor(() => expect(callback).toHaveBeenCalled(), {  timeout: 5000,  interval: 100,});

findBy — Built-in waitFor

ts
// Equivalent to: await waitFor(() => getByText('Loaded'))const element = await screen.findByText("Loaded");

waitForElementToBeRemoved

ts
await waitForElementToBeRemoved(() => screen.queryByText("Loading..."));

Common Patterns

Custom Render with Providers

tsx
// test-utils.tsximport { render } from "@testing-library/react";import { ThemeProvider } from "./ThemeProvider";import { AuthProvider } from "./AuthProvider";
function AllProviders({ children }) {  return (    <ThemeProvider>      <AuthProvider>{children}</AuthProvider>    </ThemeProvider>  );}
const customRender = (ui, options) => render(ui, { wrapper: AllProviders, ...options });
export * from "@testing-library/react";export { customRender as render };

Testing Hooks

ts
import { renderHook, act } from "@testing-library/react";
test("useCounter increments", () => {  const { result } = renderHook(() => useCounter());
  expect(result.current.count).toBe(0);
  act(() => {    result.current.increment();  });
  expect(result.current.count).toBe(1);});

Rerender with New Props

ts
const { rerender } = render(<Counter count={1} />);expect(screen.getByText("Count: 1")).toBeInTheDocument();
rerender(<Counter count={2} />);expect(screen.getByText("Count: 2")).toBeInTheDocument();

Query Within Container

ts
import { within } from "@testing-library/react";
const modal = screen.getByRole("dialog");const submitBtn = within(modal).getByRole("button", { name: /submit/i });

Debugging

ts
// Print entire DOMscreen.debug();
// Print specific elementscreen.debug(screen.getByRole("button"));
// Log available rolesimport { logRoles } from "@testing-library/react";logRoles(container);
// With prettyDOM optionsscreen.debug(undefined, 10000); // max length

jest-dom Matchers

ts
import "@testing-library/jest-dom";
expect(element).toBeInTheDocument();expect(element).toBeVisible();expect(element).toBeEnabled();expect(element).toBeDisabled();expect(element).toHaveTextContent("Hello");expect(element).toHaveValue("input value");expect(element).toHaveAttribute("href", "/home");expect(element).toHaveClass("active");expect(element).toHaveFocus();expect(element).toBeChecked();

Configuration

ts
import { configure } from "@testing-library/react";
configure({  // Custom test ID attribute  testIdAttribute: "data-my-test-id",
  // Async timeout  asyncUtilTimeout: 5000,
  // Default hidden  defaultHidden: true,
  // Throw suggestions (debugging)  throwSuggestions: true,});

❌ Prohibitions (Anti-patterns)

ts
// ❌ Don't query by class/idcontainer.querySelector(".my-class");
// ❌ Don't use container.firstChildconst { container } = render(<Component />);expect(container.firstChild).toHaveClass("active");
// ❌ Don't use fireEvent when userEvent worksfireEvent.click(button); // Use userEvent.click instead
// ❌ Don't test implementation detailsexpect(component.state.loading).toBe(false);
// ❌ Don't use waitFor with findByawait waitFor(() => screen.findByText("x")); // findBy already waits
// ❌ Don't assert inside waitFor callback (unless necessary)await waitFor(() => {  expect(mockFn).toHaveBeenCalled(); // OK - need to wait for call});

✅ Best Practices

ts
// ✅ Use screen for all queriesimport { render, screen } from "@testing-library/react";render(<Component />);screen.getByRole("button"); // Good
// ✅ Prefer userEvent over fireEventconst user = userEvent.setup();await user.click(button);
// ✅ Use findBy for async elementsconst element = await screen.findByText("Loaded");
// ✅ Use queryBy for non-existence assertionsexpect(screen.queryByText("Error")).not.toBeInTheDocument();
// ✅ Use within for scoped queriesconst form = screen.getByRole("form");within(form).getByLabelText("Email");
// ✅ Use accessible queries (role, label, text)getByRole("button", { name: /submit/i });

TextMatch Options

ts
// Exact match (default)getByText("Hello World");
// Substring matchgetByText("llo Worl", { exact: false });
// RegexgetByText(/hello world/i);
// Custom functiongetByText((content, element) => {  return element.tagName === "SPAN" && content.startsWith("Hello");});

Quick Reference

ImportUsage
renderRender component to DOM
screenQuery the rendered DOM
cleanupUnmount components (auto in Jest)
actWrap state updates
renderHookTest custom hooks
withinScope queries to element
waitForRetry until assertion passes
configureSet global options
userEvent.setup()Create user event instance

Links

來源與署名

來源:itechmeat/llm-code位於skills/react-testing-library提交7ae8a00

授權條款: 無授權條款

內容歸原作者所有。SourceWeft 從公開儲存庫中收錄這些內容。

檢舉或申請下架