home / skills / grishaangelovgh / gemini-cli-agent-skills / react-test-engineer

react-test-engineer skill

/react-test-engineer

This skill guides you in testing React applications with RTL and Vitest, focusing on user-centric, accessible, robust unit and integration tests.

npx playbooks add skill grishaangelovgh/gemini-cli-agent-skills --skill react-test-engineer

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
6.4 KB
---
name: react-test-engineer
description: 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.
---

# 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 with `expect(container).toHaveNoViolations()`.

## Vitest Setup & Configuration

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

### 1. Dependencies
Recommend installing:
`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`)
Extend Vitest's expect with DOM matchers.

```typescript
import '@testing-library/jest-dom';
import * as matchers from '@testing-library/jest-dom/matchers';
import { expect } from 'vitest';
import { cleanup } from '@testing-library/react';
import { afterEach } from 'vitest';

// Extends Vitest's expect method with methods from react-testing-library
expect.extend(matchers);

// Runs a cleanup after each test case (e.g. clearing jsdom)
afterEach(() => {
  cleanup();
});
```

## Best Practices Checklist

*   [ ] **Clean Setup:** Use `render` from RTL. Do not use `shallow` rendering.
*   [ ] **Arrange-Act-Assert:** Structure tests clearly.
*   [ ] **Avoid False Positives:** Ensure you are waiting for the UI to settle if needed.
*   [ ] **Mocks:**
    *   Mock network requests (e.g., using MSW - Mock Service Worker) rather than mocking `fetch`/`axios` directly inside components if possible.
    *   Use `vi.fn()` for creating spy functions.
    *   Use `vi.mock()` for module mocking.

## Advanced Configuration: Custom Render

Real-world applications rely on Providers (Theme, Auth, Redux, Router).

```javascript
// test-utils.tsx
import { render } from '@testing-library/react';
import { ThemeProvider } from 'my-theme-lib';
import { AuthProvider } from './context/auth';

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

const customRender = (ui, options) =>
  render(ui, { wrapper: AllTheProviders, ...options });

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

## Common Patterns

### Testing a Form
```javascript
import { render, screen } from './test-utils'; // Custom render
import userEvent from '@testing-library/user-event';
import { vi } from 'vitest';

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
```javascript
import { render, screen } from '@testing-library/react';

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

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

  // Wait for element to appear
  const userItem = await screen.findByText(/Alice/i);
  expect(userItem).toBeInTheDocument();
  
  expect(screen.queryByRole('heading', { name: /loading/i })).not.toBeInTheDocument();
});
```

### Testing Custom Hooks
Logic often resides in hooks. Use `renderHook`.

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

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

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

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

## Debugging Tips

*   **`screen.debug()`**: Prints the current DOM state to the console.
*   **`logRoles(container)`**: Helpful to see how RTL perceives the role hierarchy of your component.
    ```javascript
    import { logRoles } from '@testing-library/react';
    // ... inside test
    const { container } = render(<MyComponent />);
    logRoles(container);
    ```
*   **Vitest UI**: Recommend running `npx vitest --ui` for a visual test interface.

Overview

This skill provides expert guidance for testing React applications using React Testing Library and Vitest. It focuses on user-centric tests, accessibility checks, and maintainable unit and integration test patterns. The guidance helps teams write tests that survive refactors and reflect real user behavior.

How this skill works

It explains core principles like testing behavior over implementation, prioritizing accessibility-aware queries (getByRole, getByLabelText), and using user-event to simulate real interactions. The skill covers Vitest setup, recommended dependencies, a setup file, and an optional custom render for provider-wrapped components. It also includes patterns for forms, async loading, custom hooks, mocking strategies, and basic debugging techniques.

When to use it

  • Writing or refactoring unit and integration tests for React components
  • Adding accessibility checks and ensuring components expose proper roles/labels
  • Configuring Vitest for a React project or migrating from Jest
  • Testing components that depend on context providers, theming, or routing
  • Inspecting async UI flows such as data loading and form submissions

Best practices

  • Test user-visible behavior, not internal implementation or state
  • Prefer accessibility-first queries (getByRole with name) and use data-testid only as last resort
  • Always use @testing-library/user-event for interactions and await its async methods
  • Mock network interactions with MSW and use vi.fn()/vi.mock() for spies and module mocks
  • Keep tests clean with Arrange-Act-Assert and run cleanup after each test

Example use cases

  • Testing a login form submission flow with user typing and button click using user-event
  • Verifying an async list component shows a loading state then renders items with findBy*
  • Running automated a11y checks using vitest-axe to catch common accessibility violations
  • Creating a custom render utility to wrap components with ThemeProvider and AuthProvider
  • Testing custom hooks with renderHook and act for stateful logic

FAQ

Should I use fireEvent or user-event?

Use @testing-library/user-event; it simulates real browser interactions including focus and composition more accurately than fireEvent.

When should I use findBy vs getBy?

Use findBy* for elements that appear asynchronously. Use getBy* for synchronous assertions and queryBy*/queryAllBy* when expecting absence.

How do I add accessibility testing?

Install vitest-axe and run expect(container).toHaveNoViolations() in tests to catch a11y issues automatically.