Advanced React Testing
Learn React Testing Library for testing React components in a way that resembles how users interact with your application. This is a foundational concept in component-based UI development that professional developers rely on daily. The explanations below are written to be beginner-friendly while covering the depth and nuance that comes from real-world React experience. Take your time with each section and practice the examples
RTL Philosophy
React Testing Library focuses on testing components the way users would interact with them, making tests more reliable and maintainable.. This is an essential concept that every React developer must understand thoroughly. In professional development environments, getting this right can mean the difference between code that works reliably and code that breaks in production. The following sections break this down into clear, digestible pieces with practical examples you can try immediately
Basic Component Testing
// Basic component test
import { render, screen } from '@testing-library/react';
import Button from './Button';
test('renders button with text', () => {
render(<Button>Click me</Button>);
expect(screen.getByRole('button', { name: 'Click me' })).toBeInTheDocument();
});User Interactions
// Testing user interactions
import { render, screen, fireEvent } from '@testing-library/react';
import Counter from './Counter';
test('increments counter on button click', () => {
render(<Counter />);
const button = screen.getByRole('button', { name: /increment/i });
const count = screen.getByText('Count: 0');
fireEvent.click(button);
expect(count).toHaveTextContent('Count: 1');
});Form Testing
// Testing forms
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import LoginForm from './LoginForm';
test('submits form with valid data', async () => {
const onSubmit = jest.fn();
render(<LoginForm onSubmit={onSubmit} />);
fireEvent.change(screen.getByLabelText(/email/i), {
target: { value: 'test@example.com' }
});
fireEvent.change(screen.getByLabelText(/password/i), {
target: { value: 'password123' }
});
fireEvent.click(screen.getByRole('button', { name: /submit/i }));
await waitFor(() => {
expect(onSubmit).toHaveBeenCalledWith({
email: 'test@example.com',
password: 'password123'
});
});
});Custom Hooks Testing
// Testing custom hooks
import { renderHook, act } from '@testing-library/react';
import { useCounter } from './useCounter';
test('useCounter hook', () => {
const { result } = renderHook(() => useCounter(0));
expect(result.current.count).toBe(0);
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});Mini-Project: Complete Component Test Suite
// Complete test suite for a complex component
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { BrowserRouter } from 'react-router-dom';
import UserProfile from './UserProfile';
const renderWithRouter = (component) => {
return render(
<BrowserRouter>
{component}
</BrowserRouter>
);
};
describe('UserProfile Component', () => {
const mockUser = {
id: 1,
name: 'John Doe',
email: 'john@example.com',
avatar: 'avatar.jpg'
};
beforeEach(() => {
jest.clearAllMocks();
});
test('renders user information', () => {
renderWithRouter(<UserProfile user={mockUser} />);
expect(screen.getByText('John Doe')).toBeInTheDocument();
expect(screen.getByText('john@example.com')).toBeInTheDocument();
});
test('handles edit mode toggle', async () => {
const user = userEvent.setup();
renderWithRouter(<UserProfile user={mockUser} />);
const editButton = screen.getByRole('button', { name: /edit/i });
await user.click(editButton);
expect(screen.getByDisplayValue('John Doe')).toBeInTheDocument();
expect(screen.getByRole('button', { name: /save/i })).toBeInTheDocument();
});
test('saves user changes', async () => {
const onSave = jest.fn();
const user = userEvent.setup();
renderWithRouter(<UserProfile user={mockUser} onSave={onSave} />);
await user.click(screen.getByRole('button', { name: /edit/i }));
await user.clear(screen.getByDisplayValue('John Doe'));
await user.type(screen.getByDisplayValue(''), 'Jane Doe');
await user.click(screen.getByRole('button', { name: /save/i }));
await waitFor(() => {
expect(onSave).toHaveBeenCalledWith({
...mockUser,
name: 'Jane Doe'
});
});
});
});