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

2

6

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...

Constabulary answered 17/1, 2022 at 13:49 Comment(0)
C
7

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.

Constabulary answered 19/1, 2022 at 16:6 Comment(0)
H
0

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'))
  })
Hudnall answered 17/1, 2022 at 14:27 Comment(7)
Thank you for your answer. I believe this is what I'm already doing. I render the component, then wait for the loader (role == 'progressbar') to disappear, then open a menu and wait for the items to load before triggering a click event on one of the items and an "ESC"-key event to close the menu... Isn't that the correct approach?Constabulary
And as I mentioned, the tests pass perfectly fine on my local computer. The problem only occurs on the CI-server.Constabulary
Did you add axios.defaults.adapter = require('axios/lib/adapters/http') into test-setup.tsHudnall
I'm not using axios, I'm using fetchConstabulary
Okey, i guess you did not mock a http request. It works on localhost because you run http server.Hudnall
I'm using MSW (mock-service-worker) to mock the HTTP-requests. This works fine. The problem I'm having is inconsistency between a local test-run and a CI-test-run, where the CI-test-run is giving me the "act()"-warnings.Constabulary
I have basically the same problem. It seems like the speed of msw is affected significantly by running in the CI environment. Changing the number of workers locally doesn't result in the same issue. Could also be the speed of local processors vs the server. Might have to get a slower computer, haha.Dickdicken

© 2022 - 2024 — McMap. All rights reserved.