React RTL编写React DnD文件拖拽测试用例hover报错如何解决
问题描述
使用React DnD实现文件上传功能时,通过React Testing Library(RTL)为组件编写测试用例会抛出如下报错:
Error: Uncaught [Invariant Violation: Cannot call hover while not dragging.]
已提供可复现问题的示例沙箱。
报错原因
这个报错是React DnD内部状态机校验抛出的:当DnD上下文没有记录到处于激活状态的拖拽流程时,调用了hover逻辑就会触发该异常。测试环境下触发该问题的常见原因有两个:
- 测试时直接使用了生产环境的
HTML5Backend,jsdom没有完整实现HTML5拖拽的原生事件流,很容易跳过dragstart初始化阶段直接触发dragover事件,导致DnD内部状态未初始化。 - 手动触发拖拽事件时顺序错误,跳过了拖拽启动步骤直接触发悬停、放置事件,DnD无法识别到当前正在进行拖拽操作。
解决方案
- 测试环境替换DnD后端为官方提供的测试专用
TestBackend,不要复用生产环境的HTML5后端。封装通用的测试渲染方法即可:
import { render } from '@testing-library/react'; import { DndProvider } from 'react-dnd'; import { TestBackend } from 'react-dnd-test-backend'; export const renderWithDnd = (component: React.ReactElement) => { return render( <DndProvider backend={TestBackend}> {component} </DndProvider> ); };
- 模拟拖拽操作时严格按照拖拽生命周期触发事件,禁止跳过启动步骤直接触发hover/drop:
- 首先在拖拽源元素上触发
dragStart事件,传入mock的拖拽数据,初始化DnD拖拽状态 - 再在放置目标区域依次触发
dragEnter、dragOver事件(对应组件内的hover逻辑) - 最后触发
drop事件完成放置,再触发dragEnd事件收尾
- 首先在拖拽源元素上触发
- 文件拖拽场景需要自行mock jsdom缺失的DataTransfer对象,且mock对象要在dragStart阶段就传入,不要等到hover阶段才挂载:
// 构造mock文件和DataTransfer const testFile = new File(['dummy content'], 'test.pdf', { type: 'application/pdf' }); const mockDataTransfer = { files: [testFile], types: ['Files'], items: [], }; // 按顺序触发事件 fireEvent.dragStart(dragSourceEl, { dataTransfer: mockDataTransfer }); fireEvent.dragEnter(dropZoneEl, { dataTransfer: mockDataTransfer }); fireEvent.dragOver(dropZoneEl, { dataTransfer: mockDataTransfer }); // 此时执行hover相关断言不会再抛错 fireEvent.drop(dropZoneEl, { dataTransfer: mockDataTransfer }); fireEvent.dragEnd(dragSourceEl);
- 不要在测试用例中直接手动调用DnD monitor或者connector的hover方法,必须通过事件触发或者TestBackend提供的模拟API调度拖拽流程,避免内部状态不同步。
内容的提问来源于stack exchange,提问作者ds090918
相关产品推荐
相关产品推荐

