7

I write a test for a seletion and I get this warning. In my test I'm waiting for the end of the act. Why I get this error?

Warning: You seem to have overlapping act() calls, this is not supported. Be sure to await previous act() calls before making a new one.

test('Selection should be have the correct number of options', async () => {
  const leftClick = { button: 0 };
  const { options } = makeSUT();
  const selection = screen.getByLabelText('MultiSelection');

  // open all option
  act(() => {
    userEvent.click(selection, leftClick);
  });
  // await wait();

  options.forEach(async (option, index) => {
    if (index === 0) {
      expect((await screen.findAllByText(option.label)).length).toEqual(1);
    } else {
      expect((await screen.findAllByText(option.label)).length).toEqual(1);
    }
  });
});

Thank you

Bryan Ash
  • 4,385
  • 3
  • 41
  • 57
Andre Bongartz
  • 101
  • 1
  • 1
  • 8
  • The first item wrapped in `act` needs an `await`. RTL methods are wrapped in `act` so your assertion is using an `act` call before the first one has resolved. – Davidicus Mar 23 '23 at 14:44

2 Answers2

9

userEvent utility API methods should not be wrapped in act(). It is recommended not to do so here. Instead, you can just await the method call. After you have performed an action, you can use waitFor to wait for the Component state to update and run your assertions. To simplify your logic, I would replace findBy with a waitFor and getBy so you're not having to async your forEach().

You should ensure you are setting up userEvent correctly (see here)

The following should resolve your issue:

test('Selection should be have the correct number of options', async () => {
    const user = userEvent.setup(); // Make sure to setup correctly.
    const leftClick = { button: 0 };
    const { options } = makeSUT();
    const selection = screen.getByLabelText('MultiSelection');
    
    // Wait for the userEvent to click:
    await user.click(selection, leftClick);
    waitFor(() => {
        options.forEach((option, index) => {
            if (index === 0) {
                expect((screen.getAllByText(option.label)).length).toEqual(1);
            } else {
                expect((screen.getAllByText(option.label)).length).toEqual(1);
            }
        });
    });
});
reubennn
  • 186
  • 1
  • 7
  • 1
    Any idea why `await` works? Doesn't seem to be any mention of it in the docs. – Matt Aug 10 '22 at 15:39
  • I just checked out the docs I linked, and I can see that the example they use has `await user.click(element)`. They might not have done it in some other examples because perhaps they weren't using an async test. – reubennn Feb 08 '23 at 04:13
  • @Matt I believe all RTL methods are wrapped in act. The waitFor is probably not necessary as you are waiting on that state update to complete before you are asserting. – Davidicus Mar 23 '23 at 14:43
  • @Davidicus I found inconsistencies in external testing CI pipelines when I did not use the `waitFor` before asserting when using RTL APIs like `type` or `keyboard`. – reubennn Mar 28 '23 at 02:14
0

You shouldn't wrap userEvent.click in act, you just need to waitFor the effect of the click to be visible.

I made a couple of small changes that leave us with a very readable test:

  • Left clicking is the default, so we don't need to specify that.
  • getByText returns the one element that matches the text, so you don't need to get all of the elements and check that there's only one.
  • We're not using the index argument in forEach so we can drop that too.
test('Selection should be have the correct number of options', async () => {
  const { options } = makeSUT();

  const selection = screen.getByLabelText('MultiSelection');
  userEvent.click(selection);

  await waitFor(() => {
    options.forEach((option) => screen.getByText(option.label);
  });
});
Bryan Ash
  • 4,385
  • 3
  • 41
  • 57