使用Enzyme测试带计时器的React表格行事件时遇组件卸载错误
解决React组件单击双击计时器与单元测试的卸载问题
你遇到的核心问题是测试异步回调执行时,Enzyme挂载的组件实例已经被React卸载了。这是因为使用真实setTimeout时,Jest测试环境可能在等待回调的过程中,默认认为测试已完成(哪怕你用了done),或者组件在state更新后出现了意外卸载。另外,组件里的全局计时器变量也会导致测试用例间的干扰,加重了这个问题。
问题拆解
你的测试用了真实的300ms延迟,这段等待时间里,Jest的测试上下文可能已经发生变化,Enzyme的wrapper对应的组件实例已被卸载。当你在回调里调用rows2.at(3).simulate('click')时,React找不到对应的DOM节点,就抛出了「无法在已卸载组件上找到节点」的错误。
同时,组件里的timer和prevent是全局变量,这会导致多行事件互相干扰,也会让测试结果变得不可预测。
具体解决方案
1. 改用Jest的Fake Timers
Jest提供了模拟计时器的能力,不用等待真实时间,手动控制计时器执行即可,既加快测试速度,又能避免组件被卸载的问题。修改测试代码如下:
it('selects row on single click', (done) => { // 启用Jest模拟计时器 jest.useFakeTimers(); const mockCallback = jest.fn(); const newProps = {...props, data: data, schema: schema, onSelect: mockCallback}; const wrapper = mount(<QueryRecordsTable {...newProps} />); const rows = wrapper.find(Tooltip); expect(rows.length).toEqual(6); // 模拟第一次点击 rows.at(0).simulate('click'); // 快进计时器到延迟时间之后,同步执行setTimeout回调 jest.runAllTimers(); // 验证第一次点击的状态变化 expect(mockCallback.mock.calls[0][0]).toEqual(ID1); expect((wrapper.state() as any).selectedRecord.ID).toEqual(ID1); expect(wrapper).toMatchSnapshot(); // 模拟第二次点击 const rows2 = wrapper.find(Tooltip); rows2.at(3).simulate('click'); jest.runAllTimers(); // 这里可以添加第二次点击后的断言逻辑 // ... // 恢复真实计时器,避免影响其他测试 jest.useRealTimers(); done(); });
2. 修复组件中的全局变量问题
把组件里的timer和prevent改成组件实例的属性,避免不同行或测试用例间的干扰:
// 在组件类的构造函数或useEffect(函数组件)中初始化 this.timer = null; this.prevent = false; // 修改事件处理函数 return { onClick: e => { this.timer = setTimeout(() => { if (!this.prevent && rowInfo && rowInfo.row) { this.setSelectedRecord(rowInfo.original); this.props.onSelect && this.props.onSelect(rowInfo.original[primaryKey]); } this.prevent = false; }, delay); }, onDoubleClick: e => { clearTimeout(this.timer); this.prevent = true; if (rowInfo && rowInfo.row) { this.setSelectedRecord(null); this.props.onDoubleClick && this.props.onDoubleClick(rowInfo.original[primaryKey]); } } };
3. 测试结束后清理计时器
为避免测试用例间的干扰,在每个测试结束后清除所有计时器:
afterEach(() => { jest.clearAllTimers(); });
为什么之前的方式行不通?
使用真实setTimeout时,测试会进入等待状态,Jest的测试环境可能在这段时间里清理了未被「持续引用」的组件,导致Enzyme的wrapper虽然还在变量中,但对应的DOM节点已被移除。改用fake timers后,所有计时器操作都是同步执行的,组件不会有被卸载的机会,测试流程完全可控。
内容的提问来源于stack exchange,提问作者herbertwest
相关产品推荐
相关产品推荐

