How to Test Class Components in React
Testing class components in React is essential for maintaining legacy codebases and ensuring application stability. This guide provides a straightforward overview of how to test React class components using Jest alongside React Testing Library and Enzyme. You will learn how to test component rendering, state changes, user interactions, and lifecycle methods to ensure your application behaves as expected.
Choosing Your Testing Tools
When testing React class components, you generally choose between two primary libraries:
- React Testing Library (RTL): The modern standard recommended by the React team. It focuses on testing component behavior from the user’s perspective rather than implementation details (like internal state or lifecycle methods).
- Enzyme: A legacy testing utility by Airbnb. While deprecated in modern React, it is still widely used in older codebases because it allows direct inspection of a class component’s internal state, props, and lifecycle methods.
Testing with React Testing Library (Recommended)
React Testing Library encourages testing what the user sees on the screen. It treats class components and functional components exactly the same way.
Example Class Component
Here is a simple Counter class component:
import React, { Component } from 'react';
class Counter extends Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
increment = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<div>
<p>Current Count: {this.state.count}</p>
<button onClick={this.increment}>Increment</button>
</div>
);
}
}
export default Counter;Writing the Test
To test this component using React Testing Library and Jest, you simulate user interaction and assert that the DOM updates correctly.
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Counter from './Counter';
test('increments the count when the button is clicked', async () => {
render(<Counter />);
// Verify initial state
const countText = screen.getByText(/Current Count: 0/i);
expect(countText).toBeInTheDocument();
// Simulate user click
const button = screen.getByRole('button', { name: /increment/i });
await userEvent.click(button);
// Verify updated state in the DOM
expect(screen.getByText(/Current Count: 1/i)).toBeInTheDocument();
});Testing with Enzyme (Legacy Codebases)
If you are working on an older codebase that utilizes Enzyme, you can write unit tests that directly access the component instance, state, and methods.
Writing the Test with Shallow Rendering
Enzyme’s shallow rendering renders only the single
component without rendering its children, making unit tests fast and
isolated.
import { shallow } from 'enzyme';
import Counter from './Counter';
describe('Counter Component', () => {
it('should initialize with a count of 0', () => {
const wrapper = shallow(<Counter />);
expect(wrapper.state('count')).toBe(0);
});
it('should increment count in state when button is clicked', () => {
const wrapper = shallow(<Counter />);
// Find button and simulate click
wrapper.find('button').simulate('click');
// Assert state changed directly
expect(wrapper.state('count')).toBe(1);
});
it('should call the increment method directly', () => {
const wrapper = shallow(<Counter />);
const instance = wrapper.instance();
// Call class method directly
instance.increment();
expect(wrapper.state('count')).toBe(1);
});
});Testing Lifecycle Methods
If your class component relies on lifecycle methods like
componentDidMount or componentWillUnmount, you
can test them by asserting their side effects.
Example Component with Lifecycle Method
class DataFetcher extends Component {
state = { data: null };
componentDidMount() {
this.props.fetchData().then((data) => this.setState({ data }));
}
render() {
return <div>{this.state.data ? this.state.data : 'Loading...'}</div>;
}
}Testing the Lifecycle with Jest Mocks
You can mock the prop function to test if
componentDidMount executes it correctly upon rendering.
import { render, screen, waitFor } from '@testing-library/react';
import DataFetcher from './DataFetcher';
test('fetches data on mount', async () => {
const mockFetchData = jest.fn().mockResolvedValue('Hello World');
render(<DataFetcher fetchData={mockFetchData} />);
// Verify initial loading state
expect(screen.getByText(/Loading.../i)).toBeInTheDocument();
// Verify function call on mount
expect(mockFetchData).toHaveBeenCalledTimes(1);
// Verify DOM updates after promise resolves
await waitFor(() => {
expect(screen.getByText('Hello World')).toBeInTheDocument();
});
});