如何在Jest测试中正确等待React组件状态更新后执行断言?
解决Enzyme测试中异步状态更新的正确姿势
你现在遇到的问题是React异步状态更新和Enzyme渲染时机的典型场景——用sleep(500)这种硬等待的方式既不稳定(不同环境执行速度有差异,偶尔会因为时间不够导致测试失败),还会拖慢整体测试速度,完全没必要。下面给你两种更可靠的处理方式:
方法一:利用微任务队列 + wrapper.update()
React的状态更新和Promise解析都属于微任务,我们可以通过await Promise.resolve()让JavaScript引擎先处理完所有待执行的微任务,再手动触发Enzyme更新渲染结果,这样就能拿到最新的组件状态了。
改造后的测试代码如下:
it('adds a thing', async () => { // 用Jest的mock函数更简洁地模拟返回resolved的Promise ThingManager.default.addPlan = jest.fn().mockResolvedValue(); const wrapper = shallow(<Home />); // 先完成输入操作 const input = wrapper.find('#plan-title'); input.simulate('change', { target: { value: 'TEST ITEM' } }); // 触发添加按钮的点击事件 wrapper.find('button').simulate('click'); // 等待所有微任务(Promise解析 + React状态更新)执行完毕 await Promise.resolve(); // 强制Enzyme更新组件的渲染结果 wrapper.update(); // 现在断言就能拿到更新后的组件了 expect(wrapper.find('Thing').length).toBe(1); // 额外:还能验证addPlan是否被正确调用 expect(ThingManager.default.addPlan).toHaveBeenCalled(); });
方法二:自定义flushPromises函数
如果你的很多测试都需要处理类似异步场景,可以封装一个通用的flushPromises函数,本质和方法一一致,但代码复用性更强:
// 可以放在测试工具文件里复用 const flushPromises = () => new Promise(resolve => setImmediate(resolve)); it('adds a thing', async () => { ThingManager.default.addPlan = jest.fn().mockResolvedValue(); const wrapper = shallow(<Home />); const input = wrapper.find('#plan-title'); input.simulate('change', { target: { value: 'TEST ITEM' } }); wrapper.find('button').simulate('click'); // 刷新所有待处理的Promise和微任务 await flushPromises(); wrapper.update(); expect(wrapper.find('Thing').length).toBe(1); });
为什么原代码需要sleep?
当你点击按钮后,handleAdd里的Promise是异步执行的,React的状态更新也是异步批量处理的——这时候你的expect断言会在状态更新和组件重渲染之前执行,自然找不到<Thing>组件。而sleep(500)是靠“硬等时间”来蒙异步操作完成,显然不是可靠的做法。
内容的提问来源于stack exchange,提问作者McDerp
相关产品推荐
相关产品推荐

