Simple and complete React hooks testing utilities that encourage good testing practices.
—
Core functionality for rendering and testing React hooks in isolation, providing a test harness that runs hooks within a proper React component context.
Creates a test harness for running React hooks in isolation, returning a result container and utilities for interacting with the hook.
/**
* Renders a React hook in a test environment
* @param callback - Function that calls the hook to be tested
* @param options - Optional configuration including initial props and wrapper component
* @returns RenderHookResult with current value, utilities, and async helpers
*/
function renderHook<TProps, TResult>(
callback: (props: TProps) => TResult,
options?: RenderHookOptions<TProps>
): RenderHookResult<TProps, TResult>;
interface RenderHookOptions<TProps> {
/** Initial props to pass to the hook callback */
initialProps?: TProps;
/** React component to wrap the hook (for context providers, etc.) */
wrapper?: React.ComponentType<TProps>;
}
interface RenderHookResult<TProps, TValue> {
/** Container holding the hook's return value and history */
result: RenderResult<TValue>;
/** Re-render the hook with new props */
rerender: (newProps?: TProps) => void;
/** Unmount the hook and cleanup resources */
unmount: () => void;
/** Wait for a condition to be true */
waitFor: (callback: () => boolean | void, options?: WaitForOptions) => Promise<void>;
/** Wait for a selected value to change */
waitForValueToChange: (selector: () => unknown, options?: WaitForValueToChangeOptions) => Promise<void>;
/** Wait for the next hook update */
waitForNextUpdate: (options?: WaitForNextUpdateOptions) => Promise<void>;
}
interface RenderResult<TValue> {
/** Current return value of the hook */
readonly current: TValue;
/** Array of all values returned by the hook (including errors) */
readonly all: Array<TValue | Error>;
/** Current error if the hook threw an error */
readonly error?: Error;
}Usage Examples:
import { renderHook } from "@testing-library/react-hooks";
import { useState } from "react";
// Simple hook test
test("renders hook without props", () => {
const { result } = renderHook(() => useState(0));
expect(result.current[0]).toBe(0);
expect(typeof result.current[1]).toBe("function");
});
// Hook with initial props
function useCounter(initialCount: number) {
const [count, setCount] = useState(initialCount);
return { count, increment: () => setCount(c => c + 1) };
}
test("renders hook with initial props", () => {
const { result } = renderHook(() => useCounter(5));
expect(result.current.count).toBe(5);
});
// Hook with wrapper component (for context)
const ThemeContext = React.createContext("light");
function useTheme() {
return React.useContext(ThemeContext);
}
test("renders hook with wrapper", () => {
const wrapper = ({ children }) => (
<ThemeContext.Provider value="dark">{children}</ThemeContext.Provider>
);
const { result } = renderHook(() => useTheme(), { wrapper });
expect(result.current).toBe("dark");
});Re-renders the hook with new props, useful for testing how hooks respond to prop changes.
/**
* Re-render the hook with new props
* @param newProps - New props to pass to the hook callback (optional)
*/
rerender(newProps?: TProps): void;Usage Examples:
function useGreeting(name: string) {
return `Hello, ${name}!`;
}
test("hook responds to prop changes", () => {
const { result, rerender } = renderHook(
(props) => useGreeting(props.name),
{ initialProps: { name: "Alice" } }
);
expect(result.current).toBe("Hello, Alice!");
// Re-render with new props
rerender({ name: "Bob" });
expect(result.current).toBe("Hello, Bob!");
});Unmounts the hook and cleans up any associated resources.
/**
* Unmount the hook and cleanup resources
*/
unmount(): void;Usage Examples:
function useInterval(callback: () => void, delay: number) {
React.useEffect(() => {
const interval = setInterval(callback, delay);
return () => clearInterval(interval);
}, [callback, delay]);
}
test("hook cleans up on unmount", () => {
const callback = jest.fn();
const { unmount } = renderHook(() => useInterval(callback, 100));
// Let some time pass
jest.advanceTimersByTime(250);
expect(callback).toHaveBeenCalledTimes(2);
// Unmount and verify cleanup
unmount();
jest.advanceTimersByTime(200);
expect(callback).toHaveBeenCalledTimes(2); // No more calls
});Access to the complete history of hook return values and errors.
interface RenderResult<TValue> {
/** Current return value of the hook */
readonly current: TValue;
/** Array of all values returned by the hook throughout its lifecycle */
readonly all: Array<TValue | Error>;
/** Current error if the hook is in an error state */
readonly error?: Error;
}Usage Examples:
function useToggle(initial = false) {
const [value, setValue] = useState(initial);
const toggle = () => setValue(prev => !prev);
return { value, toggle };
}
test("tracks hook result history", () => {
const { result } = renderHook(() => useToggle(false));
expect(result.current.value).toBe(false);
expect(result.all).toHaveLength(1);
expect(result.all[0]).toEqual({ value: false, toggle: expect.any(Function) });
act(() => {
result.current.toggle();
});
expect(result.current.value).toBe(true);
expect(result.all).toHaveLength(2);
expect(result.all[1]).toEqual({ value: true, toggle: expect.any(Function) });
});Install with Tessl CLI
npx tessl i tessl/npm-testing-library--react-hooks