Testing hooks
const { current, rerender, unmount } = renderHook(CounterHook, {
initialOptions: { start: 2 },
});
expect(current.count).toBe(2);
act(() => current.increment());
expect(current.count).toBe(3);
It really mounts a component
A Ramonda hook cannot stand alone — use() gives it its owner's runtime, and that runtime is what
its lifecycle, effects and option signals hang off. So renderHook builds a throwaway host
component and uses the hook on it.
There is no lighter way that still exercises the same machinery, and a lighter way that did not would be testing something other than what ships.
current does not change between renders
Unlike a function-hook library, where the whole point is that each render returns a fresh value:
const first = result.current;
act(() => result.current.increment());
expect(result.current).toBe(first); // ✓ same object
A Ramonda hook is constructed once by use() and lives as long as its owner. The instance is
the identity; the fields are what change. Read a field to see the current value.
rerender(options) drives the real path
rerender({ start: 99 });
Options reach a hook through signals owned by the caller, updated when the caller re-renders. Passing new options here drives that same path, so anything that reacts to an option reacts exactly as it would under a real parent.
wrapper
For a hook that needs a provider above it:
renderHook(ThemedHook, { wrapper: ThemeShell });
Testing a hook through a component instead
Sometimes clearer, especially when the hook's whole job is to affect what its owner renders:
class Owner extends Component {
counter = this.use(CounterHook, { start: 5 });
render() { return <p>{this.counter.count}</p>; }
}
const { instance, getByText } = render<Owner>(<Owner />);
act(() => instance.counter.increment());
expect(getByText("6")).toBeTruthy();
Both are real. renderHook is for testing the hook's own behaviour; a component is for testing
what the hook does to a render.