reactjsunit-testingjestjsreact-testing-librarytesting-library

React Unit Test Not Able to Find an Element with Using Async Act


I am testing user click interaction with using Jest and RTL in my React app. Component states are updating with user interaction, i.e., a click event.

My component has one combo box. When clicked, it should open a list box and show options.

test.only('option should open when clicked', async () => {
  // Arrange
  const props = {
    searchProcessing: false,
    setSearchProcessing: () => {},
    setFilterInfo: () => {},
    setData: () => {},
    sortBy: {
      FieldName: 'state',
      Direction: 'ASC',
    },
    pageNumber: 1,
    pageSize: 10,
  };

  // Act
  await act(async () => {
    await render(<Component{...props} />);
    const combobox = await screen.findByRole('combobox', { owns: 'select-state' });
    fireEvent.click(combobox);
  });
  const options = within(screen.getByRole('listbox')).getAllByRole('option');
  screen.debug(options);

  // Assert
  expect(options).toHaveLength(2);
});

When I run the above code, it throws an error:

  Unable to find role="combobox"

    Ignored nodes: comments, script, style
    <body>
      <div />
    </body>

            await act(async () => {
      23 |     await render(<Component {...props} />);
    > 24 |     combobox = await screen.findByRole('combobox', { owns: 'select-state' });
         |                             ^
      25 |   });

When I put fireEvent in the waitFor async method insted of act per the below snippet, it works.

await waitFor(() => {
  fireEvent.click(screen.getByRole('combobox', { owns: 'select-state' }));
});
console.log
    <a
      aria-label="AK"
      aria-selected="false"
      class="dropdown-item"
      href="#"
      id="select-state-item-0"
      role="option"
    >
      AK
    </a>

      at logDOM (node_modules/@testing-library/dom/dist/pretty-dom.js:94:13)
          at Array.forEach (<anonymous>)

  console.log
    <a
      aria-label="AK2"
      aria-selected="false"
      class="dropdown-item"
      href="#"
      id="select-state-item-1"
      role="option"
    >
      AK2
    </a>

      at logDOM (node_modules/@testing-library/dom/dist/pretty-dom.js:94:13)
          at Array.forEach (<anonymous>)

 PASS  src/App/Main/Component.test.js (11.508 s)
  √ state option should open when clicked (454 ms)

Test Suites: 1 passed, 1 total                                                                                                                                                                                         
Tests:       1 passed, 1 total                                                                                                                                                                                         
Snapshots:   0 total
Time:        16.5 s

But according to the linting rules mentioned here: no-wait-for-side-effects, it is a bad practice.

__

What am I missing here? Why are render and findByRole promise not waiting for an actual component DOM in the act function. But the same code passed with waitFor?

Your help is appreciated here. Thanks in advance.


Solution

  • After so many trials and errors I found 2 possible solutions.

    Solution #1

    Wrap the second action/event in another act:

        await act(async () => {
        await render(<Component {...props} />);
      });
       await act(async () => {
         fireEvent.click(await screen.findByRole('combobox', { owns: 'select-state' }));
       });
    

    Solution #2

    RTL has another utility userEvent. You can use it instead of fireEvent as below.

      const user = userEvent.setup();
    await user.click(await screen.findByRole('combobox', { owns: 'select-state' }));
    

    Hope it helps. Thanks.