Skip to content
Back to skills

Testing Library

ASecurity

Testing Library for UI testing. Covers queries, user events, and async utilities. Use for React/Vue component testing. USE WHEN: user mentions "testing library", "react testing", "component test", asks about "screen.getByRole", "userEvent", "render", "fireEvent", "waitFor", "accessibility testing" DO NOT USE FOR: E2E tests - use `playwright` or `cypress`; Unit tests - use `vitest` or `jest`; API testing - use framework HTTP clients; Non-UI logic - use unit testing

  • 31 stars
  • 0 votes
  • 0 copies
  • 9 views
  • Added September 8, 2026
ai-agentsgoreactvuenodetestingapiperformancedocumentation

Works with

  • cli
  • api
  • mcp

Security analysis

A100/100

Pro scans all 3 files and shows the line behind each finding

Scanned September 8, 2026

npx -y skills add claude-dev-suite/claude-dev-suite --skill testing-library --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Testing Library?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Testing Library
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/claude-dev-suite-testing-library/badge)](https://www.skillsdirectory.com/skills/claude-dev-suite-testing-library)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: testing-library
description: |
  Testing Library for UI testing. Covers queries, user events,
  and async utilities. Use for React/Vue component testing.

  USE WHEN: user mentions "testing library", "react testing", "component test", asks about "screen.getByRole", "userEvent", "render", "fireEvent", "waitFor", "accessibility testing"

  DO NOT USE FOR: E2E tests - use `playwright` or `cypress`; Unit tests - use `vitest` or `jest`; API testing - use framework HTTP clients; Non-UI logic - use unit testing
allowed-tools: Read, Grep, Glob, Write, Edit
---
# 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

```tsx
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)

```tsx
// 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

```tsx
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

```tsx
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

```tsx
// 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

```tsx
// 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

```tsx
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

```tsx
// 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

```tsx
// 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](quick-ref/queries.md)
- [User Events](quick-ref/user-events.md)

Files in this skill

  • SKILL.md8.8 KB
  • quick-ref/queries.md4.1 KB
  • quick-ref/user-events.md4.8 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.