Skip to content

Commit 5ba7fe9

Browse files
committed
WIP: migration guide
1 parent a267f1e commit 5ba7fe9

File tree

1 file changed

+225
-0
lines changed

1 file changed

+225
-0
lines changed

MIGRATION_GUIDE.md

+225
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
# React 18 Migration guide
2+
3+
React Hooks Testing Library will not be updated to support React 18. Instead, React Testing Library
4+
and React Native Testing Library are including their own `renderHook` APIs with the goal of
5+
providing more unified and consistent experience for our users.
6+
7+
In general, the new `renderHook` functions are largely compatible with the React Hooks Testing
8+
Library version and many users will just be able to update their imports, but there are a few
9+
notable exceptions as well as some scenarios which are no longer supported at all. This guide will
10+
outline what has changed and what has been dropped and some strategies to smooth the transition.
11+
12+
## Choose your renderer
13+
14+
React Hooks Testing Library supported three different React renderers that could be used for testing
15+
hooks in different types of environments, as well as a auto-detect import that would attempt to
16+
resolve whichever renderer happened to be installed. `renderHook` could be imported using any of the
17+
following modules:
18+
19+
1. `@testing-library/react-hooks/dom` (`react-dom`), for testing hooks in a web environment
20+
2. `@testing-library/react-hooks/native` (`react-test-renderer`), for testing hooks in a
21+
`react-native` environment
22+
3. `@testing-library/react-hooks/server` (`react-dom/server`), for testing hooks in a SSR
23+
environment
24+
4. `@testing-library/react-hooks`, auto-detect either the `dom` or `native` variants based on the
25+
installed renderer
26+
27+
Depending on which renderer you were using will determine which package to migrate to.
28+
29+
- `@testing-library/react-hooks/dom`
30+
31+
```sh
32+
npm uninstall @testing-library/react-hooks
33+
npm install --save-dev @testing-library/react
34+
```
35+
36+
```diff
37+
-import { renderHook } from '@testing-library/react-hooks`;
38+
+import { renderHook } from '@testing-library/react';
39+
```
40+
41+
- `@testing-library/react-hooks/native`
42+
43+
```sh
44+
npm uninstall @testing-library/react-hooks
45+
npm install --save-dev @testing-library/react-native
46+
```
47+
48+
```diff
49+
-import { renderHook } from '@testing-library/react-hooks`;
50+
+import { renderHook } from '@testing-library/react-native';
51+
```
52+
53+
- `@testing-library/react-hooks/server`
54+
55+
> There is not an equivalent renderer for this import in the `@testing-library/react` package. You
56+
> will need to wrap the hook in your own test component and render it with `react-dom/server`
57+
> manually.
58+
59+
- `@testing-library/react-hooks`
60+
> If your project is a `react-native` app, follow the instructions above for
61+
> `@testing-library/react-hooks/native`, otherwise follow the instructions for
62+
> `@testing-library/react-hooks/dom`.
63+
64+
## `waitFor`
65+
66+
This utility should now be imported at the same time as `renderHook` instead of being accessed from
67+
the `renderHook` return value.
68+
69+
```diff
70+
-import { renderHook } from '@testing-library/react-hooks`;
71+
+import { renderHook, waitFor } from '@testing-library/react';
72+
+// or import { renderHook, waitFor } from '@testing-library/react-native';
73+
```
74+
75+
```diff
76+
-const { result, waitFor } = renderHook(() => useHook());
77+
+const { result } = renderHook(() => useHook());
78+
```
79+
80+
The React Hooks Testing Library version of `waitFor` supported either returning a `boolean` value or
81+
using assertions (e.g. `expect`) to wait for the condition to be met. Both the React Testing Library
82+
and React Native Testing Library version only support the assertion style for their `waitFor`
83+
utilities. If you were using the `boolean` style, you will need to update the callbacks like so:
84+
85+
```diff
86+
-await waitFor(() => result.current.state !== 'loading');
87+
+await waitFor(() => {
88+
+ expect(result.current.state).not.toBe('loading');
89+
+});
90+
```
91+
92+
It should also be noted that the React Hooks Testing Library version of `waitFor` would recheck the
93+
condition any time the hook triggered a render, as well as on a periodic interval but due to
94+
implementation differences of `waitFor` in the new version, the condition will only be checked on
95+
the interval. If your condition can potentially be missed by waiting for the default interval time
96+
(100ms), you may need to adjust the timings using the `interval` option:
97+
98+
```diff
99+
await waitFor(() => {
100+
expect(result.current.state).not.toBe('loading');
101+
-});
102+
+}, { interval: 20 });
103+
```
104+
105+
## `waitForValueToChange`
106+
107+
This utility has not been included in the React Testing Library or React Native Testing Library
108+
APIs. A similar result can be achieved by using `waitFor`:
109+
110+
```diff
111+
-await waitForValueToChange(() => result.current.state);
112+
+const initialValue = result.current.state;
113+
+await waitFor(() => {
114+
+ expect(result.current.state).not.toBe(initialValue);
115+
+});
116+
```
117+
118+
## `waitForNextUpdate`
119+
120+
This utility has not been included in the React Testing Library or React Native Testing Library
121+
APIs. A similar result can be achieved by using `waitFor`:
122+
123+
```diff
124+
-await waitForValueToChange(() => result.current.state);
125+
+const initialValue = result.current;
126+
+await waitFor(() => {
127+
+ expect(result.current).not.toBe(initialValue);
128+
+});
129+
```
130+
131+
Note that this is not quite the same as the previous implementation, which simply waited for the
132+
next render regardless of whether the value of `result.current` has changed or not, but this is more
133+
in line with how the utility was intended to be used. Writing tests that rely on specific timing or
134+
numbers of renders is discouraged in the Testing Library methodology as it focuses too much on
135+
implementation details of the hooks.
136+
137+
## `result.error`
138+
139+
Errors are now thrown directly from `renderHook`, `rerender` and `unmount` calls. If you were
140+
previously using `result.error` to test for error values, you should update your tests to instead
141+
check for thrown errors:
142+
143+
```diff
144+
-const { result } = renderHook(() => useHook());
145+
-expect(result.error).toBe(Error('something expected'));
146+
+expect(() => renderHook(() => useHook())).toThrow(Error('something expected'));
147+
```
148+
149+
There is an edge case that is no longer covered which is when an asynchronous update to a hook
150+
causes the next render to throw an error, e.g.
151+
152+
```ts
153+
function useAsyncValue() {
154+
const [loading, setLoading] = useState(true)
155+
const [value, setValue] = useState(null)
156+
const [error, setError] = useState(null)
157+
158+
useEffect(() => {
159+
getAsyncValue()
160+
.then(setValue)
161+
.catch(setError)
162+
.finally(() => setLoading(false))
163+
}, [])
164+
165+
if (error) {
166+
throw error
167+
}
168+
169+
return { loading, value }
170+
}
171+
```
172+
173+
In this scenario, calling `renderHook(() => useAsyncValue())` will not throw any errors. Tests that
174+
need to access an asynchronous error like this can use the `wrapper` option to wrap the hook call in
175+
an error boundary and capture the error there instead:
176+
177+
```diff
178+
+let asyncError = null;
179+
+
180+
+class ErrorBoundary extends React.Component {
181+
+ componentDidCatch(error) {
182+
+ asyncError = error;
183+
+ }
184+
+
185+
+ render() {
186+
+ return !asyncError && this.props.children;
187+
+ }
188+
+ }
189+
190+
-const { result, waitFor } = renderHook(() => useAsyncValue());
191+
+const { result } = renderHook(() => useAsyncValue(), {
192+
+ wrapper: ErrorBoundary,
193+
+});
194+
195+
await waitFor(() => {
196+
- expect(result.error).toEqual(Error('something expected'));
197+
+ expect(asyncError).toEqual(Error('something expected'));
198+
});
199+
```
200+
201+
## `result.all`
202+
203+
The new `renderHook` APIs in React Testing Library and React Native Testing Library have not
204+
included `result.all` as it was deemed to promote testing implementation details. Tests that rely on
205+
`result.all` should be rewritten to just use `result.current` and/or `waitFor` with more emphasis on
206+
testing the value that will be observed by users and not the intermediate values in between
207+
observable results.
208+
209+
## Suspense
210+
211+
Previously, React Hooks Testing Library would automatically wrap the hook call in a `Suspense`
212+
boundary. This functionality has not been replicated in either React Testing Library or React Native
213+
Testing Library so hooks that rely on suspense will need to add their own suspense boundaries using
214+
the `wrapper` option:
215+
216+
```diff
217+
+const SuspenseBoundary = ({ children }) => <Suspense fallback={null}>{children}</Suspense>
218+
219+
-const { result } = renderHook(() => useSuspendingHook());
220+
+const { result } = renderHook(() => useSuspendingHook(), {
221+
+ wrapper: SuspenseBoundary,
222+
+});
223+
```
224+
225+
## `wrapper` Props

0 commit comments

Comments
 (0)