React Testing Library tests components from the user's perspective. Use render() to mount components, query methods (getByRole, getByText, etc.) to find elements, fireEvent or userEvent for interactions, and waitFor for async updates.
Philosophy:
Query Methods:
getBy*: Throws if not found (sync)queryBy*: Returns null if not foundfindBy*: Returns Promise (async)Query Priority:
getByRole: Accessibility rolesgetByLabelText: Form elementsgetByPlaceholderTextgetByText: Non-form elementsgetByTestId: Last resortUser Events:
userEvent: More realistic than fireEvent// Counter.jsx
function Counter({ initialCount = 0 }) {
const [count, setCount] = useState(initialCount);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(c => c + 1)}>Increment</button>
<button onClick={() => setCount(c => c - 1)}>Decrement</button>
</div>
);
}
// Counter.test.jsx
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Counter from './Counter';
describe('Counter', () => {
test('renders initial count', () => {
render(<Counter initialCount={5} />);
expect(screen.getByText('Count: 5')).toBeInTheDocument();
});
test('increments count when button clicked', async () => {
const user = userEvent.setup();
render(<Counter />);
await user.click(screen.getByRole('button', { name: /increment/i }));
expect(screen.getByText('Count: 1')).toBeInTheDocument();
});
test('decrements count when button clicked', async () => {
const user = userEvent.setup();
render(<Counter initialCount={5} />);
await user.click(screen.getByRole('button', { name: /decrement/i }));
expect(screen.getByText('Count: 4')).toBeInTheDocument();
});
});