测试mock useState时是否也需要对useEffect进行mock?
错误根本原因
你当前的实现全局劫持了所有React.useState的调用,所有用到useState的位置(包括组件内其他useState、React 内部Hook逻辑、依赖useState的自定义Hook)都会返回同一个模拟的setState函数,直接破坏了React Hook的调用顺序规则,同时导致依赖该useState返回值的useEffect拿到了不符合预期的输入,才会触发length读取失败、Hook调用顺序异常的报错。
是否需要mock useEffect
不需要专门mock useEffect,你的问题本质是测试思路不合理:测试组件行为不应该通过mock底层Hook的方式验证内部实现细节,应该直接验证组件对外的表现是否符合预期。
推荐解决方案
方案1:改为测试组件实际表现(优先选择)
不要验证setState的调用情况,直接测试鼠标事件触发后组件的渲染结果是否符合预期,比如悬浮对应的提示是否展示、对应样式类是否生效等,示例代码如下:
test('should toggle hover state on mouse enter and leave', () => { // 假设悬浮时会展示类名为hover-tooltip的提示元素 expect(screen.queryByClassName('hover-tooltip')).not.toBeInTheDocument() fireEvent.mouseEnter(point) expect(screen.getByClassName('hover-tooltip')).toBeInTheDocument() fireEvent.mouseLeave(point) expect(screen.queryByClassName('hover-tooltip')).not.toBeInTheDocument() })
这种测试更稳定,不会因为组件内部state的实现逻辑调整就失效。
方案2:仅mock目标useState调用(不推荐)
如果确实需要验证setState的调用情况,不要全局mock所有useState,仅针对你要测试的那一次useState调用做模拟,其余useState保留原生实现:
test('should update state on mouse enter and mouse leave', () => { const setStateMock = jest.fn() const originUseState = React.useState let useStateCallTimes = 0 // 假设你要测试的useState是组件内第3个调用的Hook,对应调用次数为3时返回mock值 const TARGET_USESTATE_INDEX = 3 jest.spyOn(React, 'useState').mockImplementation((initValue) => { useStateCallTimes++ if (useStateCallTimes === TARGET_USESTATE_INDEX) { return [initValue, setStateMock] } return originUseState(initValue) }) point.simulate('mouseenter') expect(setStateMock).toBeCalledTimes(1) expect(setStateMock).toHaveBeenCalledWith(true) point.simulate('mouseleave') expect(setStateMock).toBeCalledTimes(2) expect(setStateMock).toHaveBeenCalledWith(false) // 测试完成后恢复原生实现 jest.restoreAllMocks() })
注意这种写法非常脆弱,只要组件内的Hook调用顺序发生变化(比如在目标useState之前新增了其他Hook),测试就会直接失效。
内容的提问来源于stack exchange,提问作者Dmitry Mishin
相关产品推荐
相关产品推荐

