如何高效可靠地测试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
相关产品推荐
相关产品推荐

