Why does destructuring need waitFor?
All tests do the same thing. In the 'ok.test.ts' file I use renderHook, I use result.current[1]
to set the state and result.current[0]
to get the value of the state, so far so good.
In 'not-ok' tests I do destructuring the result of renderHook and the tests fail because the state value is incorrect.
In 'resolved.test.ts' when using waitFor the test with destructuring works. I understand that set states is async. I didn't understand why the 'ok.test.ts' works without waitFor and without destructuring, but if I do destructuring I need waitFor.
Author of react-hooks-testing-library
here.
TL;DR; you cant destructure result.current
and have get
receive updated values.
This comes up a lot so I'll take some time to give a more detailed answer for anyone coming across this.
Firstly, in your example, the resolved
test is passing because waitFor
returns a promise that your must await
to see the failure:
// ...
await waitFor(() => {
expect(get).toStrictEqual({
data1: 1,
data2: 2,
data3: 3
});
});
// ...
In this case, it times out waiting for the expectation pass because the value never changes.
So the real question is why isn't get
(odd name BTW... state
would be more appropriate in this example) updating when set
is called?
Well, let's look at this code:
const result = {
state: 0,
setState(newState: number) {
this.state = newState;
}
};
const { state, setState } = result;
setState(1);
expect(state).toBe(1); // fails
The test fails for the same reason as your examples. Can you see why?
Well, The destructuring of result
locks the value of result.state
to whatever it was at that moment in a new variable called state
. Calling setState
(or result.setState
) will successfully update result.state
, but there is no link to the state
variable so the value does not change.
So by having const { result: { current: [get, set] } } = renderHook(...)
you are also locking get
to whatever the initial value of result.current
has and not amount of set
ting is going to allow the new variable to be updated because it's connection to result.current
has been lost.
Finally, I see this as a common accident, especially when using tuple results from hooks (e.g. const [state, setState] = useState()
). The common reason given is that people don't like referring to them as result.current[0]
and result.current[1]
in their tests. I can sympathise with that.
Another thing many people don't realise is that the value of result.current
is whatever you return from the renderHook
callback so you can easily get nicely named value by changing your renderHook
call to something like:
// ...
const { result } = renderHook(() => {
const [get, set] = React.useState({
data1: 0,
data2: 0,
data3: 0
})
return { get, set }
});
act(() => {
result.current.set({ data1: 1, data2: 2, data3: 3 });
});
expect(result.current.get).toStrictEqual({
data1: 1,
data2: 2,
data3: 3
});
});
// ...
Anyway, hope that clears it up and happy testing!