Lars Johan
Lars Johan

Reputation: 364

Why does my react tests fail in CI-pipeline due to "not wrapped in act()", while working fine locally?

I have a test-suite containing 37 tests that are testing one of my views. Locally, all tests pass without any issues, but when I push my code, the test-suite fails in our pipeline (we are using GitLab).

The error-output from the logs in CI are extremely long (thousands of lines, it even exceeds the limit set by GitLab). The error consists of many "not wrapped in act()"-, and "nested calls to act() are not supported"-warnings (Moslty triggered by useTranslation() from I18Next and componens like Tooltip from Material-UI).

My guess is that async-data from the API (mocked using msw) triggers a state-update after a call to act() has completed, but I'm not sure how to prove this, or even figure out what tests are actually failing.

Has anyone experienced something similar, or knows what's up?

Example of a failing test:

it.each([
    [Status.DRAFT, [PAGE_1, PAGE_11, PAGE_2, PAGE_22, PAGE_3]],
    [Status.PUBLISHED, [PAGE_1, PAGE_12, PAGE_2, PAGE_21, PAGE_22, PAGE_221]],
  ])('should be possible to filter nodes by status %s', async (status, expectedVisiblePages) => {
    renderComponent();
    await waitFor(() => {
      expect(screen.queryByRole('progressbar')).not.toBeInTheDocument();
    });
    userEvent.click(screen.getByLabelText('components.FilterMenu.MenuLabel'));
    const overlay = await screen.findByRole('presentation');
    await waitFor(() => expect(within(overlay).queryByRole('progressbar')).not.toBeInTheDocument());
    userEvent.click(within(overlay).getByText(`SiteStatus.${status}`));
    userEvent.keyboard('{Esc}');
    
    const items = await screen.findAllByRole('link');
    expect(items).toHaveLength(expectedVisiblePages.length);
    expectedVisiblePages.forEach((page) => expect(screen.getByText(page.title)).toBeInTheDocument());
  });

Update 1

Okay. So I've narrowed it down to this line:

    const items = await screen.findAllByRole('link');

There seems to be a lot of stuff happening while waiting for things to appear. I believed that the call to findAllByRole was already wrapped in act() and that this would make sure all updates has been applied.

Update 2

It seems to be a problem partly caused by tests timing out. I believe multiple calls to waitFor(...) and find[All]By(...) in the same test, in addition to a slow runner, collectively exceeds the timout for the test (5000ms by default). I've tried to adjust this limit by running the tests with --testTimeout 60000. And now, some of the tests are passing. I'm still struggling with the "act()"-warnings. Theese might be caused by a different problem entirely...

The bughunt continues...

Upvotes: 6

Views: 4615

Answers (2)

Lars Johan
Lars Johan

Reputation: 364

After many attempts, I finally found the answer. The CI-server only has 2 CPUs available, and by running the tests with --maxWorkers=2 --maxConcurrent=2, instead of the default --maxWorkers=100% --maxConcurrent=5, proved to solve the problem.

Upvotes: 7

Sharpek
Sharpek

Reputation: 676

This is a common issue ;)

I guess, you see this problem on CI Server because of the environment (less cpu/mem/etc).

This warning is because you do some async action but did not finish for complete it (because it's async).

You can read more about this issue in this article: https://kentcdodds.com/blog/fix-the-not-wrapped-in-act-warning

The best solution is waiting for the operation to finish. For example by adding loading indicator and waiting for element remove.

For example:

  it('should show empty table', async () => {
    const [render] = createRenderAndStore()
    mockResponse([])

    const { container } = render(<CrmClientsView />) // - this view do async request in first render
    await waitForElementToBeRemoved(screen.queryByRole('test-loading'))

    await waitFor(() => expect(container).toHaveTextContent('There is no data'))
  })

Upvotes: 0

Related Questions