testing-library

Testing Library Core Knowledge

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "testing-library" with this command: npx skills add claude-dev-suite/claude-dev-suite/claude-dev-suite-claude-dev-suite-testing-library

Testing Library Core Knowledge

Deep Knowledge: Use mcp__documentation__fetch_docs with technology: testing-library for comprehensive documentation.

When NOT to Use This Skill

  • E2E Testing - Use playwright or cypress for full browser automation

  • Unit Testing - Use vitest or jest for isolated function tests

  • API Testing - Use framework-specific HTTP testing tools

  • Performance Testing - Use Lighthouse or dedicated performance tools

  • Non-UI Logic - Test pure functions with unit tests instead

Basic Test

import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { UserForm } from './UserForm';

test('submits form with user data', async () => { const user = userEvent.setup(); const onSubmit = vi.fn();

render(<UserForm onSubmit={onSubmit} />);

await user.type(screen.getByLabelText(/name/i), 'John'); await user.type(screen.getByLabelText(/email/i), 'john@example.com'); await user.click(screen.getByRole('button', { name: /submit/i }));

expect(onSubmit).toHaveBeenCalledWith({ name: 'John', email: 'john@example.com', }); });

Queries (Priority Order)

// 1. Accessible (preferred) screen.getByRole('button', { name: /submit/i }); screen.getByLabelText(/email/i); screen.getByPlaceholderText(/search/i); screen.getByText(/welcome/i); screen.getByAltText(/logo/i); screen.getByTitle(/close/i);

// 2. Test IDs (fallback) screen.getByTestId('submit-button');

// Query variants getBy... // Throws if not found queryBy... // Returns null if not found findBy... // Async, waits for element getAllBy... // Returns array

User Events

const user = userEvent.setup();

await user.click(element); await user.dblClick(element); await user.type(input, 'text'); await user.clear(input); await user.selectOptions(select, 'option1'); await user.keyboard('{Enter}'); await user.hover(element); await user.tab();

Async Utilities

import { waitFor, waitForElementToBeRemoved } from '@testing-library/react';

// Wait for condition await waitFor(() => { expect(screen.getByText(/success/i)).toBeInTheDocument(); });

// Wait for element removal await waitForElementToBeRemoved(() => screen.queryByText(/loading/i));

// findBy is shorthand for waitFor + getBy const element = await screen.findByText(/loaded/i);

Custom Render

// test-utils.tsx function renderWithProviders(ui: React.ReactElement) { return render( <QueryClientProvider client={queryClient}> <ThemeProvider> {ui} </ThemeProvider> </QueryClientProvider> ); }

export * from '@testing-library/react'; export { renderWithProviders as render };

Production Readiness

Test Organization

// test-utils.tsx - Centralized test setup import { render, RenderOptions } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const createTestQueryClient = () => new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false }, }, });

interface WrapperProps { children: React.ReactNode; }

function AllTheProviders({ children }: WrapperProps) { const queryClient = createTestQueryClient(); return ( <QueryClientProvider client={queryClient}> <ThemeProvider> <RouterProvider> {children} </RouterProvider> </ThemeProvider> </QueryClientProvider> ); }

const customRender = ( ui: React.ReactElement, options?: Omit<RenderOptions, 'wrapper'> ) => ({ user: userEvent.setup(), ...render(ui, { wrapper: AllTheProviders, ...options }), });

export * from '@testing-library/react'; export { customRender as render };

Accessibility Testing

import { axe, toHaveNoViolations } from 'jest-axe';

expect.extend(toHaveNoViolations);

test('form should be accessible', async () => { const { container } = render(<LoginForm />); const results = await axe(container); expect(results).toHaveNoViolations(); });

// Test keyboard navigation test('should be keyboard navigable', async () => { const { user } = render(<Navigation />);

await user.tab(); expect(screen.getByRole('link', { name: /home/i })).toHaveFocus();

await user.tab(); expect(screen.getByRole('link', { name: /about/i })).toHaveFocus(); });

MSW Integration

// mocks/handlers.ts import { http, HttpResponse } from 'msw';

export const handlers = [ http.get('/api/users', () => { return HttpResponse.json([ { id: 1, name: 'John' }, ]); }), http.post('/api/users', async ({ request }) => { const body = await request.json(); return HttpResponse.json({ id: 2, ...body }, { status: 201 }); }), ];

// mocks/server.ts import { setupServer } from 'msw/node'; import { handlers } from './handlers';

export const server = setupServer(...handlers);

// setup.ts beforeAll(() => server.listen({ onUnhandledRequest: 'error' })); afterEach(() => server.resetHandlers()); afterAll(() => server.close());

// In tests test('displays users from API', async () => { render(<UserList />);

expect(await screen.findByText('John')).toBeInTheDocument(); });

Testing Patterns

// Test loading states test('shows loading spinner while fetching', async () => { render(<UserProfile userId="1" />);

expect(screen.getByRole('progressbar')).toBeInTheDocument(); await waitForElementToBeRemoved(() => screen.queryByRole('progressbar')); expect(screen.getByText('John')).toBeInTheDocument(); });

// Test error states test('shows error message on failure', async () => { server.use( http.get('/api/users/:id', () => { return HttpResponse.json({ error: 'Not found' }, { status: 404 }); }) );

render(<UserProfile userId="999" />);

expect(await screen.findByRole('alert')).toHaveTextContent('Not found'); });

// Test form validation test('shows validation errors', async () => { const { user } = render(<SignupForm />);

await user.click(screen.getByRole('button', { name: /submit/i }));

expect(screen.getByText(/email is required/i)).toBeInTheDocument(); });

Monitoring Metrics

Metric Target

Component test coverage

80%

Accessibility violations 0

Test execution time < 30s

Flaky test rate 0%

Checklist

  • Custom render with providers

  • userEvent.setup() for interactions

  • Accessible queries (getByRole, getByLabelText)

  • Accessibility testing with jest-axe

  • MSW for API mocking

  • Loading/error state tests

  • Keyboard navigation tests

  • Form validation tests

  • No implementation details tested

  • Async operations properly awaited

Anti-Patterns

Anti-Pattern Why It's Bad Solution

Using container.querySelector() Tests implementation, not behavior Use screen.getByRole, getByLabelText

Testing state directly Brittle, coupled to implementation Test rendered output, user interactions

Using fireEvent over userEvent Less realistic user interactions Use userEvent for better simulation

Not waiting for async updates Flaky tests, false failures Use findBy*, waitFor, or act()

Testing CSS classes Coupled to styling Test visual behavior or aria attributes

Multiple assertions without findBy Race conditions Use findBy for elements that appear async

Not using data-testid sparingly Defeats accessibility purpose Prefer getByRole, use testid as fallback

Quick Troubleshooting

Problem Likely Cause Solution

"Unable to find element" Wrong query or element not rendered Use screen.debug(), check query

"Not wrapped in act(...)" State update not awaited Use findBy* or waitFor

"Unable to find accessible element" Missing label or role Add aria-label, role, or label element

Test timeout Async operation not resolving Check findBy timeout, verify API mock

"Multiple elements found" Non-specific query Use more specific query or { name } option

fireEvent doesn't trigger handler Wrong event or React version Use userEvent instead of fireEvent

Reference Documentation

  • Queries

  • User Events

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

cron-scheduling

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

token-optimization

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

react-19

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

webrtc

No summary provided by upstream source.

Repository SourceNeeds Review