---
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
///
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 (
{children}
);
};
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();
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();
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();
logRoles(container);
```
* **Vitest UI**: Recommend running `npx vitest --ui` for a visual test interface.