Install
$ agentstack add skill-itechmeat-llm-code-react-testing-library ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
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)
// 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
// Images
getByAltText("Company logo");
// Title attribute (less reliable)
getByTitle("Close");
3. Test IDs (Escape Hatch)
// 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 existsqueryBy*— element may not exist (assertions likeexpect(...).not.toBeInTheDocument())findBy*— element appears asynchronously
Basic Test Pattern
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:
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
await waitFor(() => {
expect(screen.getByText("Loaded")).toBeInTheDocument();
});
// With options
await waitFor(() => expect(callback).toHaveBeenCalled(), {
timeout: 5000,
interval: 100,
});
findBy — Built-in waitFor
// Equivalent to: await waitFor(() => getByText('Loaded'))
const element = await screen.findByText("Loaded");
waitForElementToBeRemoved
await waitForElementToBeRemoved(() => screen.queryByText("Loading..."));
Common Patterns
Custom Render with Providers
// 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
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
const { rerender } = render();
expect(screen.getByText("Count: 1")).toBeInTheDocument();
rerender();
expect(screen.getByText("Count: 2")).toBeInTheDocument();
Query Within Container
import { within } from "@testing-library/react";
const modal = screen.getByRole("dialog");
const submitBtn = within(modal).getByRole("button", { name: /submit/i });
Debugging
// 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
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
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)
// ❌ 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
// ✅ 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
// 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
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: itechmeat
- Source: itechmeat/llm-code
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.