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

如何高效可靠地测试Promise拒绝后的UI回滚行为?

高效测试UI乐观更新与回滚的方案

我经常遇到这类乐观更新的业务场景:

  • 用户执行操作
  • 网站立即做UI乐观更新
  • 向服务器发起更新请求
  • 等待服务器响应
  • 若更新失败,回滚UI变更

之前用带超时的测试方式存在诸多问题:测试搭建繁琐、速度慢、易受环境变更影响、复杂场景测试难度高,还容易出现act警告。下面是更高效稳定的测试方案:

核心思路:手动控制Promise状态,摒弃超时依赖

1. 替换带超时的Mock为可控Promise

不再用setTimeout延迟触发Promise的resolve/reject,而是在Mock中保存resolve或reject的引用,在测试流程中手动触发状态变更。

示例代码:

it('rolls back optimistic update', async () => {
  let rejectUpdate;
  // 保存reject方法的引用,方便后续手动触发
  jest.mocked(doUpdate).mockReturnValue(new Promise((resolve, reject) => {
    rejectUpdate = reject;
  }));

  render(<App />);
  await screen.findByText('not done');

  // 触发用户操作,断言乐观更新生效
  userEvent.click(screen.getByText('do it'));
  await screen.findByText('Done!');

  // 手动触发更新失败,无需等待超时
  rejectUpdate(new Error('Server error'));
  // 断言UI回滚完成
  await screen.findByText('not done');
});

2. 解决act警告问题

这种方式下,所有状态变更都在测试代码的控制流程内触发,React的更新会被测试框架自动包裹在act中,不会出现测试结束后才执行更新的情况,从根源上避免act警告。

3. 复杂场景的简洁测试

比如测试“后续用户操作不覆盖未完成的乐观更新”这类场景,同样通过手动控制Promise状态实现,无需嵌套超时:

it('blocks duplicate updates during optimistic state', async () => {
  let rejectFirstUpdate;
  jest.mocked(doUpdate).mockImplementation(() => 
    new Promise((_, reject) => {
      rejectFirstUpdate = reject;
    })
  );

  render(<App />);
  await screen.findByText('not done');

  // 第一次触发操作,进入乐观更新状态
  userEvent.click(screen.getByText('do it'));
  await screen.findByText('Done!');

  // 断言操作按钮被禁用(业务逻辑:禁止重复提交)
  expect(screen.getByText('do it')).toBeDisabled();

  // 手动触发更新失败,UI回滚
  rejectFirstUpdate();
  await screen.findByText('not done');

  // 断言按钮重新可用
  expect(screen.getByText('do it')).not.toBeDisabled();
});

4. 简化测试搭建的工具函数

可以抽离通用工具函数,减少重复代码:

// 封装可控Promise工具
function createControllablePromise() {
  let resolve, reject;
  const promise = new Promise((res, rej) => {
    resolve = res;
    reject = rej;
  });
  return { promise, resolve, reject };
}

在测试中直接使用:

it('rolls back optimistic update', async () => {
  const { promise, reject } = createControllablePromise();
  jest.mocked(doUpdate).mockReturnValue(promise);

  // 后续测试逻辑同上...
});

方案优势

  • 测试速度快:完全移除不必要的超时等待,测试执行效率大幅提升
  • 稳定性高:测试逻辑完全可控,不受测试环境或工具版本变更影响
  • 维护成本低:测试代码逻辑清晰,复杂场景的测试编写更简单
  • 无act警告:所有状态变更都在测试流程内触发,符合React测试要求

内容的提问来源于stack exchange,提问作者Nathan Arthur

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 03:35:29