为什么我的反应测试在 CI-pipeline 中由于 "not wrapped in act()" 而失败,而在本地工作正常?

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

我有一个包含 37 个测试的测试套件,这些测试正在测试我的一个观点。在本地,所有测试都顺利通过,但是当我推送我的代码时,测试套件在我们的管道中失败(我们使用的是 GitLab)。

CI中日志的错误输出非常长(数千行,甚至超过了 GitLab 设置的限制)。该错误包括许多“未包含在 act() 中”- 和“不支持对 act() 的嵌套调用”-警告(Moslty 由 I18Next 的 useTranslation()Tooltip 等组件触发 Material-UI).

我的猜测是来自 API 的异步数据(使用 msw 模拟)在调用 act() 完成后触发状态更新,但我不确定如何证明这个,甚至弄清楚哪些测试实际上失败了。

有没有人经历过类似的事情,或者知道是怎么回事?

失败测试示例:

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());
  });

更新 1

好的。所以我将范围缩小到这一行:

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

在等待事物出现的过程中似乎发生了很多事情。我相信对 findAllByRole 的调用已经包含在 act() 中,这将确保所有更新都已应用。

更新 2

这似乎是部分由测试超时引起的问题。 我相信在同一测试中对 waitFor(...)find[All]By(...) 的多次调用,除了运行缓慢之外,共同超过了测试的超时(默认为 5000 毫秒)。我尝试通过 运行 测试 --testTimeout 60000 来调整此限制。现在,一些测试正在通过。我仍在与“act()”警告作斗争。这些可能完全是由不同的问题引起的...

漏洞搜寻仍在继续...

这是一个常见问题 ;)

我猜,你在 CI 服务器上看到这个问题是因为环境(less cpu/mem/etc)。

此警告是因为您执行了一些异步操作但没有完成(因为它是异步的)。

您可以在这篇文章中阅读有关此问题的更多信息:https://kentcdodds.com/blog/fix-the-not-wrapped-in-act-warning

最好的解决办法是等待操作完成。例如通过添加加载指示器并等待元素删除。

例如:

  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'))
  })

经过多次尝试,我终于找到了答案。 CI-服务器只有 2 个 CPU 可用,通过 运行 测试 --maxWorkers=2 --maxConcurrent=2,而不是默认的 --maxWorkers=100% --maxConcurrent=5,证明可以解决问题。