tezvyn:

Testing Custom Hooks with `renderHook`

AI-drafted, machine-checkedSource: oss.callstack.comintermediate

`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.

Read the original → oss.callstack.com

Get five bites like this every day.

Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.