--- id: itechmeat/llm-code/react-testing-library version: "4f64b566" license: MIT install: manual updated: 2026-07-18 --- # react-testing-library — React Testing Library guides you through writing tests that mirror real user behavior rather than internal implementation. It emphasizes querying by role, label, and text—prioritizing accessibility—and provides utilities for simulating user interactions, handling async operations, and debugging. Master query types, user events, and common patterns like custom renders and hook testing. Publisher: itechmeat · Stars: 21 · Updated: 2026-07-18 Install (manual): `git clone https://github.com/itechmeat/llm-code` ## SKILL.md # React Testing Library Skill ## Quick Navigation | Topic | Link | | ----------- | ------------------------------------------------------ | | Queries | [references/queries.md](references/queries.md) | | User Events | [references/user-events.md](references/user-events.md) | | API | [references/api.md](references/api.md) | | Async | [references/async.md](references/async.md) | | Debugging | [references/debugging.md](references/debugging.md) | | Config | [references/config.md](references/config.md) | --- ## 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 role getByRole("button", { name: /submit/i }); getByRole("textbox", { name: /email/i }); // Form fields — by label getByLabelText("Email"); // Non-interactive content — by text getByText("Welcome back!"); ``` ### 2. Semantic Queries ```ts // Images getByAltText("Company logo"); // Title attribute (less reliable) getByTitle("Close"); ``` ### 3. Test IDs (Escape Hatch) ```ts // Only when other queries don't work getByTestId("custom-element"); ``` --- ## Query Types | Type | No Match | 1 Match | >1 Match | Async | | --------------- | -------- | ------- | -------- | ----- | | `getBy...` | throw | return | throw | No | | `queryBy...` | null | return | throw | No | | `findBy...` | throw | return | throw | Yes | | `getAllBy...` | throw | array | array | No | | `queryAllBy...` | [] | array | array | No | | `findAllBy...` | throw | array | array | Yes | **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(); // 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 options await 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.tsx import { render } from "@testing-library/react"; import { ThemeProvider } from "./ThemeProvider"; import { AuthProvider } from "./AuthProvider"; function AllProviders({ children }) { return ( {children} ); } 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(); expect(screen.getByText("Count: 1")).toBeInTheDocument(); rerender(); 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 DOM screen.debug(); // Print specific element screen.debug(screen.getByRole("button")); // Log available roles import { logRoles } from "@testing-library/react"; logRoles(container); // With prettyDOM options screen.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/id container.querySelector(".my-class"); // ❌ Don't use container.firstChild const { container } = render(); expect(container.firstChild).toHaveClass("active"); // ❌ Don't use fireEvent when userEvent works fireEvent.click(button); // Use userEvent.click instead // ❌ Don't test implementation details expect(component.state.loading).toBe(false); // ❌ Don't use waitFor with findBy await 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 queries import { render, screen } from "@testing-library/react"; render(); screen.getByRole("button"); // Good // ✅ Prefer userEvent over fireEvent const user = userEvent.setup(); await user.click(button); // ✅ Use findBy for async elements const element = await screen.findByText("Loaded"); // ✅ Use queryBy for non-existence assertions expect(screen.queryByText("Error")).not.toBeInTheDocument(); // ✅ Use within for scoped queries const 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 match getByText("llo Worl", { exact: false }); // Regex getByText(/hello world/i); // Custom function getByText((content, element) => { return element.tagName === "SPAN" && content.startsWith("Hello"); }); ``` --- ## Quick Reference | Import | Usage | | ------------------- | --------------------------------- | | `render` | Render component to DOM | | `screen` | Query the rendered DOM | | `cleanup` | Unmount components (auto in Jest) | | `act` | Wrap state updates | | `renderHook` | Test custom hooks | | `within` | Scope queries to element | | `waitFor` | Retry until assertion passes | | `configure` | Set global options | | `userEvent.setup()` | Create user event instance | ## Links - [Documentation](https://testing-library.com/docs/react-testing-library/intro/) - [Releases](https://github.com/testing-library/react-testing-library/releases) - [GitHub](https://github.com/testing-library/react-testing-library) - [npm](https://www.npmjs.com/package/@testing-library/react) [View on SkillFed](https://skillfed.io/itechmeat/llm-code/react-testing-library) · [View on GitHub](https://github.com/itechmeat/llm-code)