Simple and complete React hooks testing utilities that encourage good testing practices.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
React Hooks Testing Library provides simple and complete React hooks testing utilities that encourage good testing practices. It allows you to create a simple test harness for React hooks that handles running them within the body of a function component, providing various utility functions for updating inputs and retrieving outputs of custom hooks.
npm install --save-dev react-hooks-testing-libraryDependencies:
react (peer dependency, ^16.8.0)react-test-renderer (peer dependency, ^16.8.0)import { renderHook, act } from "react-hooks-testing-library";
// testHook is also available but deprecated - use renderHook insteadFor CommonJS:
const { renderHook, act } = require("react-hooks-testing-library");
// testHook is also available but deprecated - use renderHook insteadimport { renderHook, act } from "react-hooks-testing-library";
import { useState, useCallback } from "react";
// Example custom hook
function useCounter() {
const [count, setCount] = useState(0);
const increment = useCallback(() => setCount((x) => x + 1), []);
const decrement = useCallback(() => setCount((x) => x - 1), []);
return { count, increment, decrement };
}
// Test the hook
test("should increment counter", () => {
const { result } = renderHook(() => useCounter());
act(() => result.current.increment());
expect(result.current.count).toBe(1);
});
// Test with async operations
test("should handle async updates", async () => {
const { result, waitForNextUpdate } = renderHook(() => useAsyncHook());
expect(result.current.loading).toBe(true);
await waitForNextUpdate();
expect(result.current.loading).toBe(false);
});Renders a test component that calls the provided hook callback every time it renders.
/**
* Renders a test component that calls the provided callback, including any hooks it calls
* @param callback - Function to call each render that should call one or more hooks for testing
* @param options - Optional configuration object
* @returns Object with result, utilities for testing async hooks, rerendering, and unmounting
*/
function renderHook<P, R>(
callback: (props: P) => R,
options?: {
initialProps?: P;
wrapper?: React.ComponentType;
}
): {
readonly result: {
readonly current: R;
readonly error: Error;
};
readonly waitForNextUpdate: () => Promise<void>;
readonly unmount: () => boolean;
readonly rerender: (hookProps?: P) => void;
};Usage Examples:
// Basic hook testing
const { result } = renderHook(() => useState(0));
expect(result.current[0]).toBe(0);
// Testing hooks with props
const { result, rerender } = renderHook(
({ initialCount }) => useState(initialCount),
{ initialProps: { initialCount: 5 } }
);
expect(result.current[0]).toBe(5);
// Rerender with new props
rerender({ initialCount: 10 });
expect(result.current[0]).toBe(5); // State is preserved, initialCount only used on first render
// Testing hooks that require context
const wrapper = ({ children }) => (
<ThemeProvider theme="dark">{children}</ThemeProvider>
);
const { result } = renderHook(() => useTheme(), { wrapper });
expect(result.current.theme).toBe("dark");Wrapper around react-test-renderer's act function for handling state updates and effects.
/**
* Ensures that updates related to state changes, effects, and event handlers are properly flushed
* @param callback - Function to execute within act scope
* @returns void
*/
function act(callback: () => void): void;Usage Examples:
// Wrapping state updates
const { result } = renderHook(() => useCounter());
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
// Multiple state updates
act(() => {
result.current.increment();
result.current.increment();
});
expect(result.current.count).toBe(3);Handle asynchronous operations in hooks with proper waiting mechanisms.
/**
* Returns a Promise that resolves the next time the hook renders
* Commonly used when state is updated as the result of an asynchronous action
* @returns Promise that resolves on next hook render
*/
waitForNextUpdate(): Promise<void>;Usage Examples:
// Testing async hooks
const useAsyncData = () => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchData().then((result) => {
setData(result);
setLoading(false);
});
}, []);
return { data, loading };
};
test("should load data asynchronously", async () => {
const { result, waitForNextUpdate } = renderHook(() => useAsyncData());
expect(result.current.loading).toBe(true);
expect(result.current.data).toBe(null);
await waitForNextUpdate();
expect(result.current.loading).toBe(false);
expect(result.current.data).toBeDefined();
});Re-render the hook with new props to test how it responds to prop changes.
/**
* Function to rerender the test component including any hooks called in the callback function
* @param newProps - New props to pass to the callback function for future renders
* @returns void
*/
rerender(newProps?: P): void;Usage Examples:
// Testing prop changes
const useGreeting = (name) => {
return `Hello, ${name}!`;
};
const { result, rerender } = renderHook(
({ name }) => useGreeting(name),
{ initialProps: { name: "Alice" } }
);
expect(result.current).toBe("Hello, Alice!");
rerender({ name: "Bob" });
expect(result.current).toBe("Hello, Bob!");Unmount the test component to trigger cleanup effects for useEffect hooks.
/**
* Function to unmount the test component
* Commonly used to trigger cleanup effects for useEffect hooks
* @returns boolean indicating if unmount was successful
*/
unmount(): boolean;Usage Examples:
// Testing cleanup effects
const useTimer = () => {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setCount((c) => c + 1);
}, 1000);
return () => clearInterval(timer);
}, []);
return count;
};
test("should cleanup timer on unmount", () => {
const { unmount } = renderHook(() => useTimer());
// Verify cleanup doesn't throw errors
expect(() => unmount()).not.toThrow();
});Access errors thrown during hook execution for testing error scenarios.
/**
* Access to any error thrown during hook execution
*/
interface HookResult<R> {
readonly result: {
readonly current: R;
readonly error: Error;
};
}Usage Examples:
// Testing hook errors
const useErrorProneHook = (shouldThrow) => {
if (shouldThrow) {
throw new Error("Something went wrong");
}
return "success";
};
test("should capture hook errors", () => {
const { result } = renderHook(
({ shouldThrow }) => useErrorProneHook(shouldThrow),
{ initialProps: { shouldThrow: true } }
);
expect(result.error).toBeInstanceOf(Error);
expect(result.error.message).toBe("Something went wrong");
});/**
* Options for configuring hook rendering
*/
interface RenderHookOptions<P> {
/** Initial props passed to the hook callback */
initialProps?: P;
/** React component to wrap around the rendered hook */
wrapper?: React.ComponentType;
}
/**
* Result object returned by renderHook
*/
interface RenderHookResult<P, R> {
/** Container for hook return value and any errors */
readonly result: {
/** Current return value of the hook callback */
readonly current: R;
/** Error thrown during hook execution, if any */
readonly error: Error;
};
/** Promise that resolves on next hook render */
readonly waitForNextUpdate: () => Promise<void>;
/** Function to unmount the test component */
readonly unmount: () => boolean;
/** Function to rerender with optional new props */
readonly rerender: (hookProps?: P) => void;
}/**
* @deprecated Use renderHook instead. Will be removed in a future version.
* Legacy alias for renderHook function
*/
const testHook: typeof renderHook;The testHook function is deprecated and shows a console warning. Use renderHook instead for all new code.