---
name: testing
description: Principles and patterns for writing effective React tests with Jest and React Testing Library. Use during implementation for test structure guidance, choosing test patterns, and deciding testing strategies. Emphasizes testing user behavior, not implementation details.
---
# Testing Principles (Jest + React Testing Library)
Principles and patterns for writing effective TypeScript + React tests.
## When to Use
- During implementation (tests + code in parallel)
- When testing strategy is unclear
- When structuring component or hook tests
- When choosing between test patterns
## Testing Philosophy
**Test user behavior, not implementation details**
- Test what users see and do
- Use accessible queries (getByRole, getByLabelText)
- Avoid testing internal state or methods
- Focus on public API
**Prefer real implementations over mocks**
- Use MSW (Mock Service Worker) for API mocking
- Use real hooks and contexts
- Test components with actual dependencies
- Integration-style tests over unit tests
**Coverage targets**
- Pure components/hooks: 100% coverage
- Container components: Integration tests for user flows
- Custom hooks: Test all branches and edge cases
## Workflow
### 1. Identify What to Test
**Pure Components/Hooks (Leaf types)**:
- No external dependencies
- Predictable output for given input
- Test all branches, edge cases, errors
- Aim for 100% coverage
Examples:
- Button, Input, Card (presentational components)
- useDebounce, useLocalStorage (utility hooks)
- Validation functions, formatters
**Container Components (Orchestrating types)**:
- Coordinate multiple components
- Manage state and side effects
- Test user workflows, not implementation
- Integration tests with real dependencies
Examples:
- LoginContainer, UserProfileContainer
- Feature-level components with data fetching
### 2. Choose Test Structure
**test.each() - Use when:**
- Testing same logic with different inputs
- Each test case is simple (no conditionals)
- Type-safe with TypeScript
**describe/it blocks - Use when:**
- Testing complex user flows
- Need setup/teardown per test
- Testing different scenarios
**React Testing Library Suite - Always use:**
- render() for components
- screen queries (getByRole, getByText, etc.)
- user-event for interactions
- waitFor for async operations
### 3. Write Tests Next to Implementation
```typescript
// src/features/auth/components/LoginForm.tsx
// src/features/auth/components/LoginForm.test.tsx
```
### 4. Use Real Implementations
```typescript
// ✅ Good: Real implementations
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { AuthProvider } from '../context/AuthContext'
import { LoginForm } from './LoginForm'
// MSW for API mocking (real HTTP)
import { rest } from 'msw'
import { setupServer } from 'msw/node'
const server = setupServer(
rest.post('/api/login', (req, res, ctx) => {
return res(ctx.json({ token: 'fake-token' }))
})
)
beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())
test('user can log in', async () => {
const user = userEvent.setup()
render(
)
// Real user interactions
await user.type(screen.getByLabelText(/email/i), 'test@example.com')
await user.type(screen.getByLabelText(/password/i), 'password123')
await user.click(screen.getByRole('button', { name: /log in/i }))
// Assert on user-visible changes
expect(await screen.findByText(/welcome/i)).toBeInTheDocument()
})
```
### 5. Avoid Common Pitfalls
- ❌ No waitFor(() => {}, { timeout: 5000 }) with arbitrary delays
- ❌ No testing implementation details (state, internal methods)
- ❌ No shallow rendering (use full render)
- ❌ No excessive mocking (use MSW for APIs)
- ❌ No getByTestId unless absolutely necessary (use accessibility queries)
## Test Patterns
### Pattern 1: Table-Driven Tests (test.each)
```typescript
import { render, screen } from '@testing-library/react'
import { Button } from './Button'
describe('Button', () => {
test.each([
{ variant: 'primary', expectedClass: 'btn-primary' },
{ variant: 'secondary', expectedClass: 'btn-secondary' },
{ variant: 'danger', expectedClass: 'btn-danger' }
])('renders $variant variant with class $expectedClass', ({ variant, expectedClass }) => {
render(