Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
openaec-foundation avatar

React Impl Testing

  • 11 installs
  • 6 repo stars
  • Updated July 8, 2026
  • openaec-foundation/react-claude-skill-package

Helps with testing & qa tasks.

About

react-impl-testing is a Claude Code skill for testing & qa. It helps solo builders move faster with AI-assisted development.

  • react-impl-testing
  • Testing & QA
  • AI-coding skill

React Impl Testing by the numbers

  • 11 all-time installs (skills.sh)
  • Ranked #1,539 of 2,153 Testing & QA skills by installs in the Skillselion catalog
  • Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openaec-foundation/react-claude-skill-package --skill react-impl-testing

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs11
repo stars6
Last updatedJuly 8, 2026
Repositoryopenaec-foundation/react-claude-skill-package

What it does

Helps with testing & qa tasks.

Files

SKILL.mdMarkdownGitHub ↗

react-impl-testing

Quick Reference

Installation

npm install --save-dev vitest @testing-library/react @testing-library/dom \
  @testing-library/jest-dom @testing-library/user-event jsdom

Vitest Configuration

// vitest.config.ts
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: ['./src/test/setup.ts'],
    css: true,
    include: ['src/**/*.{test,spec}.{ts,tsx}'],
  },
});
// src/test/setup.ts
import '@testing-library/jest-dom/vitest';
// tsconfig.json — add to compilerOptions.types
{ "compilerOptions": { "types": ["vitest/globals", "@testing-library/jest-dom"] } }

Query Priority (ALWAYS follow this order)

PriorityQueryWhen to Use
1getByRoleAccessible role — buttons, headings, inputs, links
2getByLabelTextForm fields with associated <label>
3getByPlaceholderTextInput with placeholder (less accessible)
4getByTextNon-interactive visible text content
5getByDisplayValueInput/textarea with current value
6getByAltTextImage alt text
7getByTitleTitle attribute
8getByTestIddata-testid — LAST RESORT ONLY

Query Variants

VariantNo Match1 Match>1 MatchAsync?
getBy...throwreturnthrowNo
queryBy...nullreturnthrowNo
findBy...throwreturnthrowYes
getAllBy...throwarrayarrayNo
queryAllBy...[]arrayarrayNo
findAllBy...throwarrayarrayYes

Critical Warnings

NEVER use Enzyme. It is deprecated and incompatible with React 18+. ALWAYS use React Testing Library.

NEVER test implementation details — internal state, instance methods, component internals, or private functions. ALWAYS test visible behavior from the user's perspective.

NEVER use getByTestId when a semantic query (getByRole, getByLabelText, getByText) is available. data-testid is a last resort that provides zero accessibility value.

ALWAYS prefer userEvent over fireEvent for simulating user interactions. userEvent fires the full event sequence a real browser would produce.

ALWAYS use screen for queries instead of destructuring from render(). It makes tests more readable and consistent.

NEVER wrap RTL interactions in act() manually — render, fireEvent, userEvent, and waitFor already handle this internally.

---

Decision Trees

Which query variant should I use?

Need to assert element EXISTS?
├── Yes, and it MUST be there → getBy... (throws on missing)
├── Yes, and I need to WAIT for it → findBy... (async, retries)
└── Maybe not there (asserting absence) → queryBy... (returns null)

fireEvent or userEvent?

Simulating user interaction?
├── Typing, clicking, selecting, tabbing → ALWAYS userEvent
├── Custom/synthetic event (resize, scroll) → fireEvent
└── Low-level DOM event testing → fireEvent

Do I need act()?

Using RTL utilities (render, fireEvent, userEvent, waitFor)?
├── Yes → act() is handled internally, do NOT add it
└── No (manual state update, direct hook call) → wrap in act()

When to use snapshot testing?

Is this a good use case for snapshots?
├── Detecting unintended UI changes → Yes, sparingly
├── Testing specific behavior or logic → No, use assertions
├── Large component tree → No, snapshots become brittle
└── Small, stable output (CSS class, data attribute) → Yes, inline snapshot

---

Patterns

Pattern 1: Basic Component Test

import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/react';
import { Greeting } from './Greeting';

describe('Greeting', () => {
  it('renders the name in a heading', () => {
    render(<Greeting name="Alice" />);

    expect(screen.getByRole('heading', { name: /alice/i })).toBeInTheDocument();
  });
});

Pattern 2: User Interaction with userEvent

import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Counter } from './Counter';

describe('Counter', () => {
  it('increments count on button click', async () => {
    const user = userEvent.setup();
    render(<Counter />);

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

    expect(screen.getByText('Count: 1')).toBeInTheDocument();
  });
});

Pattern 3: Async Operations with waitFor

import { describe, it, expect, vi } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import { UserList } from './UserList';

describe('UserList', () => {
  it('displays users after loading', async () => {
    render(<UserList />);

    expect(screen.getByText(/loading/i)).toBeInTheDocument();

    await waitFor(() => {
      expect(screen.getByText('Alice')).toBeInTheDocument();
    });

    expect(screen.queryByText(/loading/i)).not.toBeInTheDocument();
  });
});

Pattern 4: Testing Custom Hooks with renderHook

import { describe, it, expect } from 'vitest';
import { renderHook, act } from '@testing-library/react';
import { useCounter } from './useCounter';

describe('useCounter', () => {
  it('starts at initial value', () => {
    const { result } = renderHook(() => useCounter(5));
    expect(result.current.count).toBe(5);
  });

  it('increments the counter', () => {
    const { result } = renderHook(() => useCounter(0));

    act(() => {
      result.current.increment();
    });

    expect(result.current.count).toBe(1);
  });
});

Pattern 5: Testing with Context Providers

import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/react';
import { ThemeProvider } from './ThemeContext';
import { ThemedButton } from './ThemedButton';

function renderWithTheme(ui: React.ReactElement, theme: string = 'light') {
  return render(ui, {
    wrapper: ({ children }) => (
      <ThemeProvider value={theme}>{children}</ThemeProvider>
    ),
  });
}

describe('ThemedButton', () => {
  it('applies dark theme class', () => {
    renderWithTheme(<ThemedButton>Click</ThemedButton>, 'dark');

    expect(screen.getByRole('button', { name: /click/i })).toHaveClass('dark-theme');
  });
});

Pattern 6: API Mocking with vi.fn()

import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { SearchForm } from './SearchForm';
import * as api from './api';

vi.mock('./api');

describe('SearchForm', () => {
  beforeEach(() => {
    vi.mocked(api.search).mockResolvedValue([
      { id: '1', title: 'React Testing' },
    ]);
  });

  it('displays search results', async () => {
    const user = userEvent.setup();
    render(<SearchForm />);

    await user.type(screen.getByRole('searchbox'), 'react');
    await user.click(screen.getByRole('button', { name: /search/i }));

    await waitFor(() => {
      expect(screen.getByText('React Testing')).toBeInTheDocument();
    });

    expect(api.search).toHaveBeenCalledWith('react');
  });
});

Pattern 7: Error State Testing

import { describe, it, expect, vi } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import { DataLoader } from './DataLoader';
import * as api from './api';

vi.mock('./api');

describe('DataLoader', () => {
  it('displays error message on fetch failure', async () => {
    vi.mocked(api.fetchData).mockRejectedValue(new Error('Network error'));

    render(<DataLoader />);

    await waitFor(() => {
      expect(screen.getByRole('alert')).toHaveTextContent(/network error/i);
    });
  });
});

Pattern 8: Snapshot Testing (Use Sparingly)

import { describe, it, expect } from 'vitest';
import { render } from '@testing-library/react';
import { Badge } from './Badge';

describe('Badge', () => {
  it('matches snapshot for status variant', () => {
    const { asFragment } = render(<Badge status="success">Done</Badge>);
    expect(asFragment()).toMatchSnapshot();
  });

  it('renders correct class via inline snapshot', () => {
    const { container } = render(<Badge status="error">Fail</Badge>);
    expect(container.firstChild).toMatchInlineSnapshot(`
      <span class="badge badge-error">Fail</span>
    `);
  });
});

---

React 18 vs 19 Testing Differences

FeatureReact 18React 19
act() warningsFrequent in async testsImproved — fewer false warnings
Form actionsTest via onSubmit handlerTest via action prop with FormData
ref in testsUse forwardRef wrapperDirect ref prop works
SuspenseLimited hydration testingFull Suspense integration
use() hookN/ATest components that read promises in render

---

Reference Links

  • references/examples.md -- Complete test examples for all patterns
  • references/api-table.md -- React Testing Library API reference
  • references/anti-patterns.md -- Common testing mistakes and fixes

Official Sources

  • https://testing-library.com/docs/react-testing-library/intro
  • https://testing-library.com/docs/react-testing-library/api
  • https://testing-library.com/docs/queries/about
  • https://testing-library.com/docs/user-event/intro
  • https://vitest.dev/guide/
  • https://react.dev/learn/testing

Related skills

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.