How to Write Unit Tests for React Components with Jest

Frontend Lead
January 18, 2025
Updated on March 2, 2025
0 MIN READ
#microservices#api#react#write#unit

Introduction

Unit testing is a critical practice in modern React development that helps ensure your components work as expected while preventing regressions. Jest, a popular JavaScript testing framework developed by Facebook, is particularly well-suited for testing React components due to its simplicity, speed, and powerful features like snapshot testing and mocking.

In this guide, we'll explore best practices for writing effective unit tests for React components using Jest. We'll cover essential testing techniques, common patterns, and practical examples to help you build more reliable React applications.

Setting Up Jest for React Testing

Before writing tests, you need to properly configure Jest in your React project. If you're using Create React App, Jest is already included and configured. For custom setups, you'll need to install these packages:

npm install --save-dev jest @testing-library/react @testing-library/jest-dom @testing-library/user-event

Here's a basic jest.config.js configuration for React:

module.exports = {
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['@testing-library/jest-dom/extend-expect'],
  moduleNameMapper: {
    '\\.(css|less|scss)$': 'identity-obj-proxy',
  },
  transform: {
    '^.+\\.(js|jsx|ts|tsx)$': 'babel-jest',
  },
};

Key configuration points:

  • testEnvironment: 'jsdom' enables DOM testing
  • setupFilesAfterEnv loads jest-dom for enhanced assertions
  • moduleNameMapper handles CSS imports
  • transform ensures JSX/TSX files are processed

Testing Basic React Components

Let's start with testing a simple presentational component. Consider this Button component:

import React from 'react'; import PropTypes from 'prop-types'; function Button({ label, onClick, disabled = false }) { return ( <button onClick={onClick} disabled={disabled} aria-disabled={disabled} > {label} </button> ); } Button.propTypes = { label: PropTypes.string.isRequired, onClick: PropTypes.func.isRequired, disabled: PropTypes.bool, }; export default Button;

Here's how we might test this component:

import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import Button from './Button'; describe('Button component', () => { it('renders with correct label', () => { const label = 'Click me'; render(<Button label={label} onClick={() => {}} />); expect(screen.getByText(label)).toBeInTheDocument(); }); it('calls onClick when clicked', () => { const handleClick = jest.fn(); render(<Button label="Test" onClick={handleClick} />); fireEvent.click(screen.getByText('Test')); expect(handleClick).toHaveBeenCalledTimes(1); }); it('disables button when disabled prop is true', () => { render(<Button label="Test" onClick={() => {}} disabled={true} />); const button = screen.getByText('Test'); expect(button).toBeDisabled(); expect(button).toHaveAttribute('aria-disabled', 'true'); }); });

Key testing principles demonstrated:

  • Test component rendering with expected props
  • Verify event handlers are called correctly
  • Check accessibility attributes
  • Use jest.fn() for mock functions
  • Leverage @testing-library/react utilities

Testing Complex Components with Hooks and Context

Testing components that use hooks or context requires additional setup. Let's test a component that uses useState and useEffect:

import React, { useState, useEffect } from 'react'; function Counter({ initialValue = 0 }) { const [count, setCount] = useState(initialValue); useEffect(() => { document.title = `Count: ${count}`; }, [count]); return ( <div> <p data-testid="count-display">{count}</p> <button onClick={() => setCount(c => c + 1)}>Increment</button> </div> ); }

Here's how we might test this component:

import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import Counter from './Counter'; describe('Counter component', () => { it('initializes with correct value', () => { render(<Counter initialValue={5} />); expect(screen.getByTestId('count-display')).toHaveTextContent('5'); }); it('increments count when button is clicked', () => { render(<Counter />); const button = screen.getByText('Increment'); fireEvent.click(button); expect(screen.getByTestId('count-display')).toHaveTextContent('1'); }); it('updates document title when count changes', () => { render(<Counter />); const button = screen.getByText('Increment'); fireEvent.click(button); expect(document.title).toBe('Count: 1'); }); });

For components using Context, you'll need to wrap them in a provider during testing:

import React from 'react'; import { render, screen } from '@testing-library/react'; import { ThemeProvider } from './ThemeContext'; import ThemedButton from './ThemedButton'; describe('ThemedButton', () => { it('renders with light theme by default', () => { render( <ThemeProvider> <ThemedButton /> </ThemeProvider> ); expect(screen.getByRole('button')).toHaveClass('light-theme'); }); });

Advanced Testing Techniques

Snapshot Testing

Snapshot tests help detect unexpected changes in your component's output:

it('matches snapshot', () => { const { asFragment } = render(<Button label="Snapshot" onClick={() => {}} />); expect(asFragment()).toMatchSnapshot(); });

Mocking Modules

Jest makes it easy to mock external dependencies:

jest.mock('../api', () => ({
  fetchData: jest.fn(() => Promise.resolve({ data: 'mock data' })),
}));

import { fetchData } from '../api';
import DataFetcher from './DataFetcher';

describe('DataFetcher', () => {
  it('displays data after fetching', async () => {
    render(<DataFetcher />);
    await screen.findByText('mock data');
    expect(fetchData).toHaveBeenCalledTimes(1);
  });
});

Testing Custom Hooks

To test custom hooks, use @testing-library/react-hooks:

import { renderHook, act } from '@testing-library/react-hooks'; import useCounter from './useCounter'; describe('useCounter', () => { it('increments count', () => { const { result } = renderHook(() => useCounter()); act(() => { result.current.increment(); }); expect(result.current.count).toBe(1); }); });

Conclusion

Writing effective unit tests for React components with Jest is essential for building maintainable, reliable applications. By following the patterns and techniques covered in this guide, you can:

  1. Test component rendering and behavior in isolation
  2. Verify proper handling of props, state, and events
  3. Test components that use hooks and context
  4. Implement snapshot testing to catch unintended changes
  5. Mock external dependencies effectively

Remember that good tests should be:

  • Focused on behavior rather than implementation details
  • Readable and maintainable
  • Fast and isolated
  • Covering critical paths and edge cases

As you continue testing your React components, explore additional Jest features like coverage reports, custom matchers, and test hooks to further enhance your testing strategy.

Share this article