Testing Custom Hooks with `renderHook`
renderHook isolates a hook for testing by running it inside a tiny, dedicated component. Use it to verify a hook's logic and side effects without rendering a full UI. Footgun: Forgetting to wrap state updates in act() can cause flaky, unpredictable tests.
Why it exists
Hooks can't be called outside of a React component. To test a custom hook's logic in isolation, you need a way to run it within a component's lifecycle without actually building and rendering a complex UI just for your test.
The mental model
Think of renderHook as creating a temporary, invisible component whose only job is to run your hook. It's a test harness that gives you a control panel to interact with the hook and observe its behavior from the outside, like a logic analyzer for your hook's state and effects.
How it works
You call await renderHook(() => yourHook(props)). It returns an object. The result.current property holds whatever your hook returns. You can call functions returned by your hook (like an increment function), but you must wrap these calls in act() to ensure React processes the resulting state updates before your test continues. The async rerender function lets you simulate new props, and the async unmount function lets you test cleanup logic in useEffect.
When to use it
Use renderHook to test any custom hook you write. It's perfect for verifying state management logic (like in useCounter), data fetching, or any behavior that involves side effects (useEffect). It is also the correct way to test hooks that consume context, by passing a provider component as a wrapper in the options.
When not to use it
Do not use renderHook to test the visual output of a component that uses your hook. For that, use the standard render function to test the full component. renderHook is for testing the hook's logic in isolation, not its effect on the final rendered output.
One canonical example
To test a simple useCounter hook, you first call const { result } = await renderHook(() => useCounter()). Your initial assertion is expect(result.current.count).toBe(0). To test the increment function, you wrap the call in act: await act(() => { result.current.increment() }). Finally, you assert the new state: expect(result.current.count).toBe(1). This pattern isolates the hook's logic from any specific UI.
Interview question
For which testing scenario is renderHook explicitly stated as an unsuitable tool?
- a.Validating the internal state transitions of a custom hook.
- b.Isolating and testing the logic of a hook that interacts with React Context.
- c.Confirming that a hook's useEffect cleanup logic executes properly.
- d.Testing how a custom hook affects the rendered DOM structure of a component.Correct
Why? this is the answer
The card explicitly states that renderHook should not be used to test the visual output of a component that uses a hook; its purpose is to test the hook's logic in isolation. Validating internal state transitions, as in option A, is a primary use case for renderHook.
Just read this? Test yourself on what you have been reading.
Read the original → oss.callstack.com
Put your scrolling time to good use
Learn one idea, try a quiz and save useful cards for revision. Tezvyn makes it easy to learn and stay current in your tech field, a few minutes at a time.
The iPhone app is on the way
We are building it. Until it lands, nothing here is held back from you: every interview card, your saved cards, streaks and the job board all work in Safari, plus hundreds of free practice quizzes of thirty questions each. Sign in and it all carries over to the app the day it arrives.
Want it as an icon? Tap Share at the bottom of Safari, then Add to Home Screen. It opens full screen and the cards you have read stay available offline.
We are hiring for this. Open roles that interview on react — each one lists the topics its interview covers.
See open roles