How to Mock useEffect in React
Mocking the useEffect hook in React is a common task
when unit testing components that perform side effects, such as API
calls or event subscriptions. This article explains how to effectively
handle useEffect during testing using Jest and React
Testing Library. You will learn why mocking the hook directly is usually
discouraged, how to mock the external dependencies called inside the
hook instead, and how to spy on useEffect when absolutely
necessary.
The Recommended Approach: Mocking the Dependency
In most cases, you should not mock the useEffect hook
itself. useEffect is an implementation detail of your
component. Instead, you should mock the external modules, API clients,
or functions that are called inside the useEffect
hook.
For example, if your component fetches data inside
useEffect:
import React, { useState, useEffect } from 'react';
import { fetchUserData } from './api';
export function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUserData(userId).then(data => setUser(data));
}, [userId]);
if (!user) return <div>Loading...</div>;
return <div>{user.name}</div>;
}To test this component, mock the fetchUserData function
using Jest:
import { render, screen, waitFor } from '@testing-library/react';
import { UserProfile } from './UserProfile';
import { fetchUserData } from './api';
// Mock the API module
jest.mock('./api');
test('renders user data after fetching', async () => {
// Define mock behavior
fetchUserData.mockResolvedValueOnce({ name: 'John Doe' });
render(<UserProfile userId="123" />);
// Verify loading state
expect(screen.getByText('Loading...')).toBeInTheDocument();
// Wait for the useEffect side effect to resolve and update the DOM
await waitFor(() => {
expect(screen.getByText('John Doe')).toBeInTheDocument();
});
});Mocking useEffect Directly (SpyOn Approach)
If you have a specific edge case where you must prevent
useEffect from running entirely, or you need to assert that
it was called, you can mock the hook using jest.spyOn.
Here is how to spy on and mock the implementation of
useEffect:
import React from 'react';
import { render } from '@testing-library/react';
import { MyComponent } from './MyComponent';
test('should spy on useEffect', () => {
const useEffectSpy = jest.spyOn(React, 'useEffect');
// Optionally mock the implementation to do nothing
useEffectSpy.mockImplementation((f) => f());
render(<MyComponent />);
expect(useEffectSpy).toHaveBeenCalled();
// Restore the original implementation after the test
useEffectSpy.mockRestore();
});Note that mocking useEffect to execute synchronously (as
done with (f) => f()) can alter the standard React
lifecycle and lead to unexpected rendering behavior. Use this approach
sparingly.
Summary of Best Practices
- Avoid Mocking the Hook: Test the outcome of the
side effect (e.g., changes in the DOM) rather than the execution of
useEffectitself. - Mock External APIs: Use
jest.mock()to mock network requests, timers, or third-party libraries invoked inside the hook. - Use act() or waitFor: Ensure your tests wrap state updates caused by asynchronous side effects using utilities from React Testing Library to prevent “not wrapped in act(…)” warnings.