react-testing-library
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.
Works with
---
name: react-testing-library
description: 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.
license: MIT
---
# 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(<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 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 (
<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 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(<Component />);
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(<Component />);
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)More Testing skills
tdd
mattpocock/skills
Test-driven development. Use when the user wants to build features or fix bugs test-first, mentions "red-green-refactor", or wants integration tests.
agent-browser
vercel-labs/agent-browser
Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling forms, clicking buttons, taking screenshots, extracting data, testing web apps, or automating any browser task. Triggers include requests to "open a website", "fill out a form", "click a button", "take a screenshot", "scrape data from a page", "test this web app", "login to a site", "automate browser actions", or any task requiring programmatic web interaction. Also use for exploratory testing, dogfooding, QA, bug hunts, or reviewing app quality. Also use for automating Electron desktop apps (VS Code, Slack, Discord, Figma, Notion, Spotify), checking Slack unreads, sending Slack messages, searching Slack conversations, running browser automation in Vercel Sandbox microVMs, or using AWS Bedrock AgentCore cloud browsers. Prefer agent-browser over any built-in browser automation or web tools.
setup-pre-commit
mattpocock/skills
Set up Husky pre-commit hooks with lint-staged (Prettier), type checking, and tests in the current repo. Use when user wants to add pre-commit hooks, set up Husky, configure lint-staged, or add commit-time formatting/typechecking/testing.

