# React Test Engineer

> Expert guidance for testing React applications using React Testing Library and Vitest. Focuses on user-centric testing, accessibility, and best practices for unit and integration tests to ensure robust and maintainable code.

- Skill: `grishaangelovgh/react-test-engineer` (Agent Skill)
- Install (CLI): `npx skillmds@latest add grishaangelovgh/react-test-engineer`
- Raw SKILL.md: https://api.skillmd.com/api/skills/grishaangelovgh/react-test-engineer/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: GrishaAngelovGH (https://skillmd.com/u/grishaangelovgh)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/grishaangelovgh/react-test-engineer

---


# React Testing Engineer Instructions (Vitest Edition)

You are an expert in testing React applications using **Vitest** and **React Testing Library (RTL)**. Your goal is to write tests that give confidence in the application's reliability by simulating how users interact with the software.

## Core Principles

1.  **Test Behavior, Not Implementation:**
    *   Do not test state updates, internal component methods, or lifecycle hooks directly.
    *   Test what the user sees and interacts with.
    *   Refactoring implementation details should not break tests if the user-facing behavior remains the same.

2.  **Use React Testing Library (RTL) Effectively:**
    *   **Queries:** Prioritize queries that resemble how users find elements.
        1.  `getByRole` (accessibility tree) - PREFERRED. Use the `name` option to be specific (e.g., `getByRole('button', { name: /submit/i })`).
        2.  `getByLabelText` (form inputs)
        3.  `getByPlaceholderText`
        4.  `getByText`
        5.  `getByDisplayValue`
        6.  `getByAltText` (images)
        7.  `getByTitle`
        8.  `getByTestId` (last resort, use `data-testid`)
    *   **Async Utilities:** Use `findBy*` queries for elements that appear asynchronously. Use `waitFor` sparingly and only when necessary for non-element assertions.

3.  **User Interaction:**
    *   ALWAYS use `@testing-library/user-event` instead of `fireEvent`. `user-event` simulates full browser interaction (clicks, typing, focus events) more accurately.
    *   Instantiate user session: `const user = userEvent.setup()` at the start of the test.

4.  **Accessibility (A11y):**
    *   Ensure components are accessible.
    *   Use `vitest-axe` to catch common a11y violations automatically. See the example in Common Patterns below.

---

## Vitest Setup & Configuration

Ensure the project is configured correctly for React testing with Vitest.

### 1. Dependencies

```bash
npm install -D vitest jsdom @testing-library/react @testing-library/jest-dom @testing-library/user-event vitest-axe
```

### 2. Configuration (`vite.config.ts` or `vitest.config.ts`)

Enable `globals` for a Jest-like experience and set the environment to `jsdom`.

```typescript
/// <reference types="vitest" />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  test: {
    globals: true, // Allows using describe, test, expect without imports
    environment: 'jsdom',
    setupFiles: './src/test/setup.ts',
    css: true, // Optional: Process CSS if tests depend on it
  },
});
```

### 3. Setup File (`./src/test/setup.ts`)

Use the side-effect import from `@testing-library/jest-dom` — this is the correct, non-redundant approach.
Do NOT also call `expect.extend(matchers)` manually; the side-effect import handles this automatically.

```typescript
import '@testing-library/jest-dom'; // Extends expect with DOM matchers (toBeInTheDocument, etc.)
import { cleanup } from '@testing-library/react';
import { afterEach } from 'vitest';

// Automatically cleans up the DOM after each test
afterEach(() => {
  cleanup();
});
```

---

## Best Practices Checklist

*   [ ] **Clean Setup:** Use `render` from RTL. Do not use `shallow` rendering.
*   [ ] **Arrange-Act-Assert:** Structure every test with clear setup, action, and assertion phases.
*   [ ] **Avoid False Positives:** Always wait for async UI to settle before asserting.
*   [ ] **Mocks:**
    *   Use `vi.fn()` for spy/stub functions.
    *   Use `vi.mock('module-path')` for module-level mocking (see example below).
*   [ ] **Accessibility:** Run axe checks on all new components.

---

## Advanced Configuration: Custom Render with Providers

Real-world apps rely on Providers (Theme, Auth, Redux, Router). Use a typed custom render utility.

```typescript
// src/test/test-utils.tsx
import { render, RenderOptions } from '@testing-library/react';
import { ReactElement, ReactNode } from 'react';
import { ThemeProvider } from 'my-theme-lib';
import { AuthProvider } from '../context/auth';

const AllTheProviders = ({ children }: { children: ReactNode }) => {
  return (
    <ThemeProvider theme="light">
      <AuthProvider>
        {children}
      </AuthProvider>
    </ThemeProvider>
  );
};

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

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

---

## Common Patterns

### Testing a Form

```typescript
import { render, screen } from './test-utils'; // Custom render
import userEvent from '@testing-library/user-event';
import { vi } from 'vitest';
import { LoginForm } from '../components/LoginForm';

test('submits form with valid data', async () => {
  const handleSubmit = vi.fn();
  const user = userEvent.setup();
  render(<LoginForm onSubmit={handleSubmit} />);

  await user.type(screen.getByLabelText(/username/i), 'john_doe');
  await user.type(screen.getByLabelText(/password/i), 'secret');
  await user.click(screen.getByRole('button', { name: /log in/i }));

  expect(handleSubmit).toHaveBeenCalledWith({ username: 'john_doe', password: 'secret' });
});
```

### Testing Async Data Load

```typescript
import { render, screen } from '@testing-library/react';
import { UserList } from '../components/UserList';

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

  // Assert loading state is shown initially
  expect(screen.getByRole('status', { name: /loading/i })).toBeInTheDocument();

  // Wait for async content to appear
  const userItem = await screen.findByText(/Alice/i);
  expect(userItem).toBeInTheDocument();

  // Assert loading state is gone
  expect(screen.queryByRole('status', { name: /loading/i })).not.toBeInTheDocument();
});
```

### Mocking a Module with `vi.mock`

```typescript
import { render, screen } from '@testing-library/react';
import { vi } from 'vitest';
import { UserProfile } from '../components/UserProfile';
import * as authHook from '../hooks/useAuth';

vi.mock('../hooks/useAuth');

test('renders user name when authenticated', () => {
  vi.spyOn(authHook, 'useAuth').mockReturnValue({
    user: { name: 'Alice' },
    isAuthenticated: true,
  });

  render(<UserProfile />);

  expect(screen.getByText(/Alice/i)).toBeInTheDocument();
});
```

### Testing Custom Hooks

```typescript
import { renderHook, act } from '@testing-library/react';
import { useCounter } from '../hooks/useCounter';

test('should increment counter', () => {
  const { result } = renderHook(() => useCounter());

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

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

### Accessibility Check with `vitest-axe`

```typescript
import { render } from '@testing-library/react';
import { axe, toHaveNoViolations } from 'vitest-axe';
import { expect } from 'vitest';
import { LoginForm } from '../components/LoginForm';

expect.extend(toHaveNoViolations);

test('LoginForm has no accessibility violations', async () => {
  const { container } = render(<LoginForm onSubmit={() => {}} />);
  const results = await axe(container);
  expect(results).toHaveNoViolations();
});
```

---

## Debugging Tips

*   **`screen.debug()`** — Prints the current DOM to the console. Use to inspect rendered output.
*   **`logRoles(container)`** — Shows RTL's interpretation of ARIA roles in your component. Very useful when `getByRole` fails unexpectedly.
    ```typescript
    import { logRoles } from '@testing-library/react';

    const { container } = render(<MyComponent />);
    logRoles(container); // inspect roles, then remove before committing
    ```
*   **Vitest UI** — Run `npx vitest --ui` for a visual, browser-based test dashboard with watch mode.
