Testing Custom Hooks
In this page:
import { renderHook, act } from "@testing-library/react";
const { result } = renderHook(() => useHookName(initial));
act(() => {
result.current.action();
});
expect(result.current.value).toBe(expected);
Why You Can't Just Call a Hook in a Test
Hooks like useState rely on React's internal rendering machinery to track state between renders — calling useCounter() directly in a plain test function throws an error, since there's no component or render cycle for it to hook into.
const result = useMyHook() directly at the top level of a test file always fails with an 'Invalid hook call' error.Using renderHook to Test in Isolation
renderHook() from @testing-library/react wraps your hook in a minimal, invisible test component automatically, letting the hook run inside a real render cycle. The returned result object's .current property gives you whatever the hook returned.
Example: Using renderHook to Test in Isolation
// Run in your local React project (npm install required)
import { renderHook } from '@testing-library/react';
function useCounter() {
const [count, setCount] = React.useState(0);
return { count, increment: () => setCount(c => c + 1) };
}
test('counter starts at 0', () => {
const { result } = renderHook(() => useCounter());
expect(result.current.count).toBe(0);
});
⚠️ This example uses an npm package with no CDN build available here — run this in your local React project.
Triggering Updates with act()
Calling a function returned by the hook (like increment()) needs to be wrapped in act(), which tells React to fully process the resulting state update and re-render before your test continues, ensuring result.current reflects the latest value.
Example: Triggering Updates with act()
// Run in your local React project (npm install required)
import { renderHook, act } from '@testing-library/react';
function useCounter() {
const [count, setCount] = React.useState(0);
return { count, increment: () => setCount(c => c + 1) };
}
test('counter increments', () => {
const { result } = renderHook(() => useCounter());
act(() => { result.current.increment(); });
expect(result.current.count).toBe(1);
});
⚠️ This example uses an npm package with no CDN build available here — run this in your local React project.
- Trying to call a hook directly in a test function body, outside of any component — hooks can only run inside React's rendering system.
- Forgetting that state updates inside renderHook's act() calls need to be wrapped for React to process them correctly.
- Testing a hook completely in isolation when its real behavior only makes sense combined with a specific component context.
- Hooks can't be called directly in a test — they need to run inside React's render cycle.
- renderHook() from @testing-library/react wraps a hook in a minimal test component automatically.
- The result object's .current property gives access to whatever the hook returns.
- act() ensures state updates triggered during a test are fully processed before assertions run.
Requires npm install @testing-library/react — runs in a Node test environment, not this browser sandbox.
Chapter Quiz — Complete all 9 topics to unlock
0/9 topics done
Complete these topics first: