Component Testing
Master component testing strategies including testing props, state, lifecycle methods, and complex component interactions. 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
Testing Component Props
Learn to test how components handle different prop values and edge cases.. 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
Testing State Changes
// Testing component state
import { render, screen, fireEvent } from '@testing-library/react';
import Toggle from './Toggle';
test('toggles state on click', () => {
render(<Toggle />);
const toggle = screen.getByRole('switch');
expect(toggle).not.toBeChecked();
fireEvent.click(toggle);
expect(toggle).toBeChecked();
});Testing Effects and Lifecycle
// Testing useEffect
import { render, screen, waitFor } from '@testing-library/react';
import DataFetcher from './DataFetcher';
test('fetches data on mount', async () => {
const mockFetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ data: 'test' })
})
);
global.fetch = mockFetch;
render(<DataFetcher />);
await waitFor(() => {
expect(screen.getByText('test')).toBeInTheDocument();
});
expect(mockFetch).toHaveBeenCalledWith('/api/data');
});Testing Error Boundaries
// Testing error boundaries
import { render, screen } from '@testing-library/react';
import ErrorBoundary from './ErrorBoundary';
const ThrowError = ({ shouldThrow }) => {
if (shouldThrow) {
throw new Error('Test error');
}
return <div>No error</div>;
};
test('catches and displays error', () => {
render(
<ErrorBoundary>
<ThrowError shouldThrow={true} />
</ErrorBoundary>
);
expect(screen.getByText('Something went wrong')).toBeInTheDocument();
});Testing Context Providers
// Testing context providers
import { render, screen } from '@testing-library/react';
import { ThemeProvider } from './ThemeContext';
import ThemedButton from './ThemedButton';
test('uses theme from context', () => {
render(
<ThemeProvider theme="dark">
<ThemedButton>Click me</ThemedButton>
</ThemeProvider>
);
const button = screen.getByRole('button');
expect(button).toHaveClass('dark-theme');
});Mini-Project: Complex Component Test
// Complete test for a complex component
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { QueryClient, QueryClientProvider } from 'react-query';
import UserDashboard from './UserDashboard';
const createTestQueryClient = () => new QueryClient({
defaultOptions: {
queries: { retry: false },
mutations: { retry: false }
}
});
const renderWithProviders = (component) => {
const queryClient = createTestQueryClient();
return render(
<QueryClientProvider client={queryClient}>
{component}
</QueryClientProvider>
);
};
describe('UserDashboard', () => {
test('renders user dashboard with data', async () => {
renderWithProviders(<UserDashboard userId={1} />);
await waitFor(() => {
expect(screen.getByText('Loading...')).toBeInTheDocument();
});
await waitFor(() => {
expect(screen.getByText('User Dashboard')).toBeInTheDocument();
});
});
test('handles user actions', async () => {
const user = userEvent.setup();
renderWithProviders(<UserDashboard userId={1} />);
await waitFor(() => {
expect(screen.getByText('User Dashboard')).toBeInTheDocument();
});
const editButton = screen.getByRole('button', { name: /edit profile/i });
await user.click(editButton);
expect(screen.getByText('Edit Profile')).toBeInTheDocument();
});
});