How to Mock useCallback Hook in React
Mocking the useCallback hook in React is a highly
effective way to control function memoization behavior, isolate
components during unit tests, and assert whether specific callbacks are
triggered. While standard testing practices usually favor testing
component behavior rather than React’s internal hooks, certain scenarios
require spying on or overriding useCallback to verify
performance optimizations or integration logic. This guide demonstrates
how to mock useCallback using Jest and React Testing
Library with direct, practical examples.
Why Mock useCallback?
The useCallback hook returns a memoized version of a
callback function that only changes if one of the dependencies has
changed. You might want to mock it to: * Verify if the memoized function
is being called with the correct arguments. * Force the callback to
bypass memoization and run on every render during a test. * Simplify the
behavior of complex event handlers in child components.
Method 1: Mocking useCallback Globally
If you want to mock useCallback across all tests in a
file, you can mock the entire react package. This approach
intercepts the hook and returns a simple mock function, while preserving
the rest of React’s built-in functionality.
import React from 'react';
import { render, fireEvent } from '@testing-library/react';
import MyComponent from './MyComponent';
// Mock the react module
jest.mock('react', () => {
const originalReact = jest.requireActual('react');
return {
...originalReact,
// Mock useCallback to immediately return the passed function
useCallback: jest.fn((fn) => fn),
};
});
describe('Mocking useCallback Globally', () => {
it('should call the mocked useCallback implementation', () => {
const { getByRole } = render(<MyComponent />);
// Assertions go here
expect(React.useCallback).toHaveBeenCalled();
});
});In this setup, jest.requireActual('react') ensures that
other essential hooks like useState and
useEffect continue to work normally, while only
useCallback is replaced with a spy.
Method 2: Spying on React.useCallback
Instead of mocking the entire module, you can spy on
React.useCallback if you import React as an object in your
test file. This is useful when you only want to mock the hook for a
specific test case and restore its original behavior afterward.
import React from 'react';
import { render } from '@testing-library/react';
import MyComponent from './MyComponent';
describe('Spying on React.useCallback', () => {
let useCallbackSpy;
beforeEach(() => {
// Create a spy on the useCallback method
useCallbackSpy = jest.spyOn(React, 'useCallback');
});
afterEach(() => {
// Restore the original implementation after each test
useCallbackSpy.mockRestore();
});
it('should track calls to useCallback', () => {
render(<MyComponent />);
// Verify that the hook was registered during rendering
expect(useCallbackSpy).toHaveBeenCalled();
});
it('should provide a custom implementation for a single test', () => {
// Override the hook behavior for this specific test
useCallbackSpy.mockImplementation((callback) => {
return (...args) => {
console.log('Mocked callback executed');
return callback(...args);
};
});
render(<MyComponent />);
});
});Method 3: Mocking the Dependency Function Directly (Recommended Alternative)
Instead of mocking the useCallback hook itself, it is
often easier and cleaner to mock the actual function or module that the
callback calls. This avoids altering React’s internal rendering
engine.
Consider a component that wraps a function inside a
useCallback:
// MyComponent.js
import React, { useCallback } from 'react';
import { submitData } from './api';
export default function MyComponent() {
const handleClick = useCallback(() => {
submitData();
}, []);
return <button onClick={handleClick}>Submit</button>;
}To test this without touching useCallback, mock the
dependency API module:
// MyComponent.test.js
import React from 'react';
import { render, fireEvent } from '@testing-library/react';
import MyComponent from './MyComponent';
import { submitData } from './api';
// Mock the API module
jest.mock('./api', () => ({
submitData: jest.fn(),
}));
describe('Testing callback behavior via dependency mocking', () => {
it('should call submitData when the button is clicked', () => {
const { getByRole } = render(<MyComponent />);
fireEvent.click(getByRole('button', { name: /submit/i }));
expect(submitData).toHaveBeenCalledTimes(1);
});
});This approach tests the actual user-facing behavior and ensures your test suite remains resilient to React framework updates.