RTL waitFor
Note
TLDR
Whenever you waitFor
, try to assert something that will never happen.
This way, you can test that your test actually works, before pushing to production 😃
// The test needs to be async
it('flips', async () => {
render(<CoinFlipper />);
expect(screen.getByText('tails')).toBeVisible();
shortlistButton.click();
// waitFor needs to be awaited
await waitFor(() => {
expect(screen.getByText('heads')).toBeVisible();
});
});
See Async Methods | Testing Library
Context
Often when writing React tests, you'll want to test that a component is reactive.
That is, that something about it changes when you do something to it.
The issue is that the change to a component can often take multiple render cycles to happen.
Given a component like:
export const CoinFlipper = () => {
const [isHeads, setIsHeads] = useState(false);
const flip = () => {
setIsHeads(!isHeads);
}
return (
<button onClick={flip}>{isHeads ? "heads" : "tails"}</button>
);
}
A test like this:
it('flips', async () => {
render(<CoinFlipper />);
expect(screen.getByText('tails')).toBeVisible();
shortlistButton.click();
expect(screen.getByText('heads')).toBeVisible();
});
Would fail.
This is because <CoinFlipper />
has only been rendered once, and so hasn't had a chance to re-render the new value in.
In this case, you could rerender()
.
However, in most more advanced setups (fetching data, waiting for events to fire), even your synchronous rerender will be too fast.
Pitfalls
It can be very easy to make a waitFor()
which always passes.
Look at waitFor
with suspicion.
it('flips', () => {
/* ... */
// This is not being awaited, so the test doesn't have a chance to fail
waitFor(() => expect(screen.getByText('heads')).toBeVisible());
});