- ✅ Supports React 19.
- ✅ Server-Side Rendering (SSR) support.
- ✅ Easy-to-use API.
- ✅ Errors reporting, even for SSR.
- ✅ Compatible with any testing framework.
In May 2022,
@testing-library/react-hooks
was announced as deprecated, leaving its users locked to React 17. The so-called "merge" with
@testing-library/react
was never fully realized, as the provided renderHook
offers only a
fraction of the functionality that @testing-library/react-hooks
had, and it completely lacks SSR
support.
I had hoped that the release of React 19 and server components would bring changes, but nothing has improved despite React 19 being in RC for almost a year before its release. When asking about SSR support, the response is often to resolve the issues independently.
This package was created to meet the needs of the hooks library I have been developing. I encourage
everyone to contribute and help make hooks testing simple again. This package offers improved
typings compared to @testing-library/react-hooks
, is testing-framework agnostic, supports SSR, and
is easy to use.
This package aims to be compatible only with the current major version of React. Additionally,
it encourages the use of newer React APIs, such as completely ditching synchronous rendering
and support of synchronous act
behavior, as React documentation states it will
be deprecated in future releases.
Since the library is designed to be testing-framework agnostic, it does not have any automatic setup
and therefore requires a tiny bit of pre-configuration to work with your testing framework. I'm
using vitest
and will provide examples for it, but all known frameworks have similar setup
functionality.
As this library is tested through testing other hooks, following configuration is applied to this repository and can be used as an example.
After adding @ver0/react-hooks-testing
to your dev-dependencies, create a file with the following
content and add it to your test runner configuration as a global setup file.
import {hooksCleanup} from "@ver0/react-hooks-testing";
import {afterEach} from 'vitest';
afterEach(hooksCleanup);
import {defineConfig} from 'vitest/config';
export default defineConfig({
test: {
dir: './src',
setupFiles: [
'./src/tests/setup/react-hooks.test.ts',
],
},
});
Or, if you don't have many hooks to test, add it directly to your test files.
All this code does - unmounts all rendered hooks after each test, so you don't have to worry about it yourself.
renderHook
function made close to @testing-library/react
API-wise, but with several differences,
that are dictated by act
function.
The act
function provided by this library is similar to the one from react
, but with stricter
typings. It only allows async functions to be passed to it, ensuring it always returns a promise,
which should be awaited. This is in line with the React documentation,
which states that synchronous
act
will be deprecated in future releases.
Additionally, this act
function bypasses
the console error
regarding the testing environment configuration. Since you're using this library to test hooks,
you're already in a testing environment, making this error redundant.
import {expect, test} from "vitest";
import {act, renderHook} from "@ver0/react-hooks-testing";
test('should use setState value', async () => {
const {result} = await renderHook(() => useState('foo'))
expect(result.value).toBe("foo")
expect(result.error).toBe(undefined)
if (result.value === undefined) {
return;
}
await act(async () => {
result.value[1]('bar')
})
expect(result.value[0]).toBe('bar')
})
As you can see in above example - renderHook
function is asynchronous, reason for that is React's
request to perform any rendering within the act
function.
In contrast to @testing-library/react
, result object has the following type definition:
/**
* Represents the result of rendering a hook.
*/
export type ResultValue<T> = {
readonly value: undefined;
readonly error: Error;
} | {
readonly value: T;
readonly error: undefined;
}
/**
* Represents the last rendered hook result and all previous results.
*/
export type ResultValues<T> = ResultValue<T> & {
readonly all: ResultValue<T>[];
}
The render results history is accessible via the all
property, which contains every rendering
result as well as the value of the latest render. Unlike @testing-library/react
, which collects
results during the effect phase, this library populates the results during the render phase - it
allows to ensure tested hook results correctness throughout each render.
Another notable difference is the error
property. This property captures any error thrown during
the hook’s rendering, thanks to an Error Boundary component wrapped around the hook’s harness
component.
Each result object is immutable and contains either a value
or an error
property—but never both.
The hook’s result object follows the same principle. Although the value
and error
properties are
implemented as getters and always exist, the values they return correspond to the most recent render
result from the all
array.
Otherwise, the API is similar to @testing-library/react
. Note that the waitForNextUpdate
function is not provided, as modern testing frameworks include their own waitFor
function, which
serves the same purpose.
The primary purpose of this package is to provide out-of-the-box SSR (Server-Side Rendering) support
through the renderHookServer
function. This function works similarly to renderHook
but includes
a few key differences:
- Initial Render: The initial render is performed using React's
renderToString
function. - Hydration: The hook's render result includes an extra
hydrate
function that hydrates the hook after the initial render. - DOM Root Creation: The client-side DOM root is created only when the
hydrate
function is called, facilitating testing in a server environment. - Rerenders: Rerenders are only possible after hydration.
Otherwise, usage is similar to renderHook
, so dedicated examples are not provided. For usage
examples, refer to the *.ssr.test.ts
files in this repository.