Skip to content
tezvyn:

Testing Custom Hooks with `renderHook`

Source: oss.callstack.comMediumHow cards are made

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?

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.

Get it on Google PlayiPhone app coming soon

We are hiring for this. Open roles that interview on react — each one lists the topics its interview covers.

See open roles