You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React测试中Promise.all调用userEvent.click报重叠act()调用错误求解决

问题说明

我在编写React组件测试时遇到了一个问题:当尝试用Promise.all批量点击已选中的复选框以取消选中时,测试会报错,但如果硬编码两次单独的点击操作,测试就能正常通过。我希望找到一种用循环或批量处理的方式来实现这个逻辑,而不是硬编码。

测试代码如下:

it('2 checkmarks are checked after clicking on them and unchecked when clicked a 2nd time', async () => {
  const userViewer = userEvent.setup();
  const testComponent = render(<TestContainer />);

  // 展开折叠面板项
  const expandAccordionItemControl = testComponent.getByRole('button', { name: /Add groups/i });
  await userViewer.click(expandAccordionItemControl);

  // 点击两个行复选框
  let checkedRows = testComponent.queryAllByRole('checkbox', { checked: true });
  expect(checkedRows.length).toBe(0);
  const uncheckedRows = testComponent.getAllByRole('checkbox', { checked: false });
  const checkboxTwo = uncheckedRows[1];
  const checkboxFour = uncheckedRows[3];
  await userViewer.click(checkboxTwo);
  await userViewer.click(checkboxFour);

  // 验证两个复选框已选中
  checkedRows = testComponent.queryAllByRole('checkbox', { checked: true });
  expect(checkedRows.length).toBe(2);

  // 取消选中已勾选的复选框
  // await Promise.all(checkedRows.map((cb) => userViewer.click(cb))); <-------- 此处报错
  await act(async () => {
    await Promise.all(checkedRows.map((cb) => userViewer.click(cb))); // <--------- 尝试用act包裹仍报错
  });

  // 验证所有复选框都已取消选中
  checkedRows = testComponent.queryAllByRole('checkbox', { checked: true });
  expect(checkedRows.length).toBe(0);
});

目前可行的硬编码方案:

const checkedBoxTwo = checkedRows[0];
const checkedBoxFour = checkedRows[1];
await userViewer.click(checkedBoxTwo);
await userViewer.click(checkedBoxFour);
解决思路

问题出在Promise.all的并行执行机制上:它会同时触发所有复选框的点击操作,而React的状态更新、DOM渲染都是异步过程,并行操作会导致状态变更冲突,DOM元素的选中状态来不及同步更新,最终引发测试错误。

要解决这个问题,需要串行执行每个点击操作,确保前一个点击完成(状态更新、DOM渲染完毕)后,再执行下一个点击。以下是两种可行的实现方式:

方式一:使用for...of循环

这是最直观的串行处理方式:

// 替换原来的Promise.all代码块
for (const checkbox of checkedRows) {
  await userViewer.click(checkbox);
}

方式二:使用reduce串行执行Promise

如果偏好函数式写法,可以用reduce来按顺序执行每个点击:

// 替换原来的Promise.all代码块
await checkedRows.reduce(async (previousPromise, checkbox) => {
  await previousPromise;
  return userViewer.click(checkbox);
}, Promise.resolve());

注意事项

  • 不要使用forEach循环:forEach不支持异步等待,即便在回调里写await,所有点击操作仍会并行触发,无法解决问题。
  • 无需额外包裹act:userEvent的click方法已经内部处理了React的状态更新,不需要手动用act包裹。

内容的提问来源于stack exchange,提问作者Fiddle Freak

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 17:45:43