Learn comprehensive testing strategies for React applications using Jest, React Testing Library, and advanced testing techniques.
Learn comprehensive testing strategies for React applications using Jest, React Testing Library, and advanced testing techniques.
Master Jest testing framework for writing comprehensive unit tests and test utilities for React applications.
Content by: Praveen Kumar
MERN Stack Developer
Jest is a JavaScript testing framework that provides a complete testing solution with built-in mocking, assertions, and test runners.
// jest.config.js
module.exports = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/src/setupTests.js'],
moduleNameMapping: {
'^@/(.*)$': '<rootDir>/src/$1'
},
collectCoverageFrom: [
'src/**/*.{js,jsx,ts,tsx}',
'!src/**/*.d.ts'
]
};
// setupTests.js
import '@testing-library/jest-dom';// Basic test example
describe('Math utilities', () => {
test('adds two numbers correctly', () => {
expect(2 + 2).toBe(4);
});
test('handles edge cases', () => {
expect(() => {
throw new Error('Something went wrong');
}).toThrow('Something went wrong');
});
});// Mock functions
const mockFn = jest.fn();
mockFn('arg1', 'arg2');
expect(mockFn).toHaveBeenCalledWith('arg1', 'arg2');
// Mock modules
jest.mock('./api', () => ({
fetchUser: jest.fn(() => Promise.resolve({ id: 1, name: 'John' }))
}));// Testing async functions
test('fetches user data', async () => {
const user = await fetchUser(1);
expect(user).toEqual({ id: 1, name: 'John' });
});
// Testing promises
test('handles promise rejection', async () => {
await expect(fetchUser(999)).rejects.toThrow('User not found');
});// Complete testing suite for utility functions
import { formatDate, validateEmail, debounce } from '../utils';
describe('Utility Functions', () => {
describe('formatDate', () => {
test('formats date correctly', () => {
const date = new Date('2023-12-25');
expect(formatDate(date)).toBe('25/12/2023');
});
test('handles invalid date', () => {
expect(formatDate('invalid')).toBe('Invalid Date');
});
});
describe('validateEmail', () => {
test('validates correct email', () => {
expect(validateEmail('test@example.com')).toBe(true);
});
test('rejects invalid email', () => {
expect(validateEmail('invalid-email')).toBe(false);
});
});
describe('debounce', () => {
test('debounces function calls', (done) => {
const mockFn = jest.fn();
const debouncedFn = debounce(mockFn, 100);
debouncedFn();
debouncedFn();
debouncedFn();
setTimeout(() => {
expect(mockFn).toHaveBeenCalledTimes(1);
done();
}, 150);
});
});
});Test your understanding of this topic:
Learn React Testing Library for testing React components in a way that resembles how users interact with your application.
Content by: Sachin Patel
Node.js Developer
React Testing Library focuses on testing components the way users would interact with them, making tests more reliable and maintainable.
// 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();
});// 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');
});// 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'
});
});
});// 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);
});// 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'
});
});
});
});Test your understanding of this topic:
Master component testing strategies including testing props, state, lifecycle methods, and complex component interactions.
Content by: Parth Patel
Node.js Developer
Learn to test how components handle different prop values and edge cases.
// 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 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
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
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');
});// 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();
});
});Test your understanding of this topic:
Learn integration testing strategies to test how multiple components work together and interact with external systems.
Content by: Bansi Patel
Node.js Developer
Integration testing verifies that different parts of your application work together correctly, including components, APIs, and external services.
// Testing component integration
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
const renderWithRouter = (component) => {
return render(
<BrowserRouter>
{component}
</BrowserRouter>
);
};
test('complete user flow', async () => {
renderWithRouter(<App />);
// Navigate to login
fireEvent.click(screen.getByText('Login'));
// Fill login form
fireEvent.change(screen.getByLabelText(/email/i), {
target: { value: 'test@example.com' }
});
fireEvent.change(screen.getByLabelText(/password/i), {
target: { value: 'password123' }
});
// Submit form
fireEvent.click(screen.getByRole('button', { name: /login/i }));
// Verify navigation to dashboard
await waitFor(() => {
expect(screen.getByText('Dashboard')).toBeInTheDocument();
});
});// Testing API integration
import { render, screen, waitFor } from '@testing-library/react';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
import UserList from './UserList';
const server = setupServer(
rest.get('/api/users', (req, res, ctx) => {
return res(ctx.json([
{ id: 1, name: 'John Doe' },
{ id: 2, name: 'Jane Smith' }
]));
})
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
test('fetches and displays users', async () => {
render(<UserList />);
await waitFor(() => {
expect(screen.getByText('John Doe')).toBeInTheDocument();
expect(screen.getByText('Jane Smith')).toBeInTheDocument();
});
});// MSW setup for API mocking
import { rest } from 'msw';
import { setupServer } from 'msw/node';
const handlers = [
rest.get('/api/users', (req, res, ctx) => {
return res(ctx.json({ users: [] }));
}),
rest.post('/api/users', (req, res, ctx) => {
return res(ctx.json({ id: 1, ...req.body }));
})
];
export const server = setupServer(...handlers);// Testing error handling
test('handles API errors gracefully', async () => {
server.use(
rest.get('/api/users', (req, res, ctx) => {
return res(ctx.status(500), ctx.json({ error: 'Server error' }));
})
);
render(<UserList />);
await waitFor(() => {
expect(screen.getByText('Error loading users')).toBeInTheDocument();
});
});// Complete integration test suite
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { BrowserRouter } from 'react-router-dom';
import { QueryClient, QueryClientProvider } from 'react-query';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
import App from './App';
const server = setupServer(
rest.get('/api/users', (req, res, ctx) => {
return res(ctx.json([
{ id: 1, name: 'John Doe', email: 'john@example.com' }
]));
}),
rest.post('/api/users', (req, res, ctx) => {
return res(ctx.json({ id: 2, ...req.body }));
})
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
const renderApp = () => {
const queryClient = new QueryClient({
defaultOptions: {
queries: { retry: false },
mutations: { retry: false }
}
});
return render(
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<App />
</BrowserRouter>
</QueryClientProvider>
);
};
describe('App Integration Tests', () => {
test('complete user management flow', async () => {
const user = userEvent.setup();
renderApp();
// Navigate to users page
await user.click(screen.getByText('Users'));
// Wait for users to load
await waitFor(() => {
expect(screen.getByText('John Doe')).toBeInTheDocument();
});
// Add new user
await user.click(screen.getByRole('button', { name: /add user/i }));
await user.type(screen.getByLabelText(/name/i), 'Jane Smith');
await user.type(screen.getByLabelText(/email/i), 'jane@example.com');
await user.click(screen.getByRole('button', { name: /save/i }));
// Verify new user appears
await waitFor(() => {
expect(screen.getByText('Jane Smith')).toBeInTheDocument();
});
});
});Test your understanding of this topic:
Continue your learning journey and master the next set of concepts.
Continue to Module 12