javascriptreactjsunit-testingtestingreact-testing

Why do I need waitFor or act instead of just using await in react testing?


  1. I read that act / waitFor is to wait until the DOM is updated. I've also read that Render is synchronous. I assume that I don't need to wrap a Render with act/waitFor as the DOM will always be updated by the time I do a click or a test ? Is this correct ?

  2. Next, I don't understand why I need act / waitFor ? Can't I just do a await ? For Test A, I use user.click and I saw the online sample code uses await. It seems to work well on my laptop.

    However in Test B, when I see some sample codes for fireEvent.click, they use waitFor. I tried using await, it flag me "await has not effect on this type of expression". But "await waitFor()" works. Why is this ?

Thanks very much in advance !

Test A
it('user.click', async () => {
  render(
    <BrowserRouter>
      <Notes />
    </BrowserRouter>
  );

  const checkBox = screen.getByRole('checkbox');
  await user.click(checkBox);
  const prev = screen.getByRole('button', { name: 'Prev' });
  expect(prev).toBeEnabled();
});

Test B
it('fireEvent.click', async () => {
  render(
    <BrowserRouter>
      <Notes />
    </BrowserRouter>
  );

  const checkBox = screen.getByRole('checkbox');
  await fireEvent.click(checkBox);
  const prev = screen.getByRole('button', { name: 'Prev' });
  expect(prev).toBeEnabled();
});

Solution

  • I tried using await, it flag me "await has not effect on this type of expression". But "await waitFor()" works. Why is this ?

    await is syntactic sugar for attaching callbacks to a promise; the rest of your asynchronous function will continue once the promise has resolved or rejected.

    So, for example, it doesn't make sense to write something like await 3, because 3 isn't a promise; there isn't anything to wait for. (It's actually legal to write await 3, but it doesn't really accomplish anything; you could just write 3.)

    So the reason that await user.click(...) and await waitFor(...) are both fine, whereas await fireEvent.click(...) is not, is that user.click and waitFor both return promises, whereas fireEvent.click does not. fireEvent.click just dispatches the event and returns synchronously. (Actually I'm not sure that user.click does anything inherently asynchronous, either — it doesn't seem to depend on any APIs like fetch — but presumably the Testing Library maintainers want to keep their options open in case there comes to be a real need for user.click to support something that has to be asynchronous. But waitFor genuinely has to be asynchronous no matter what, because its entire purpose is to poll a callback every so often until it succeeds.)

    1. I read that act / waitFor is to wait until the DOM is updated. I've also read that Render is synchronous. I assume that I don't need to wrap a Render with act/waitFor as the DOM will always be updated by the time I do a click or a test ? Is this correct ?

    So, there are two kinds of "asynchronous":

    Now that you understand that, this question is simple to answer: you don't need to use act with render, because render actually calls act internally for you; and you don't need to use await with render, because rendering doesn't involve any truly asynchronous APIs. So you can just use render directly.