使用Jest测试结合SetTimeout和useEffect的自定义Hook失败如何解决
问题定位
- Redux相关依赖未正确mock
你使用了useAppDispatch自定义Hook,但测试中没有对它进行mock,导致dispatch方法未正确初始化。同时你对profileSlice的mock逻辑错误,声明的logoutmock函数和模块导出的logout没有关联,即使dispatch执行也不会触发你监听的mock函数。
- Redux相关依赖未正确mock
- 时间单位不匹配
你的Hook逻辑中写了exp * 1000,说明设计上exp是秒级Unix时间戳,但测试代码中Date.now() + 6000生成的是毫秒级时间戳,传入Hook后乘以1000会得到远大于预期的超时时间,导致定时器不会在你预期的时机触发。
- 时间单位不匹配
- 伪定时器下Date.now()不匹配
你在test用例内部才调用jest.useFakeTimers(),而exp是在describe块顶部用真实的Date.now()生成的,调用useFakeTimers()后Hook内部的Date.now()会被重置为默认值0,计算出来的timeToLogout会出现异常,甚至出现你观察到的exp为0的假象。
- 伪定时器下Date.now()不匹配
- renderHook使用方式有误
renderHook本身已经内部处理了act包裹,不需要额外套一层act,错误的包裹可能会导致Hook的参数传递、执行时机出现异常。
- renderHook使用方式有误
修复后的测试代码
import { renderHook } from '@testing-library/react-hooks'; import useTokenExpirationCheck from './useTokenExpirationCheck'; import { logout } from '../features/profile/profileSlice'; import { useAppDispatch } from '../store/hooks'; // mock Redux相关依赖 jest.mock('../store/hooks', () => ({ useAppDispatch: jest.fn() })); const mockDispatch = jest.fn(); (useAppDispatch as jest.Mock).mockReturnValue(mockDispatch); // 正确关联logout mock jest.mock('../features/profile/profileSlice', () => ({ ...jest.requireActual('../features/profile/profileSlice'), logout: jest.fn() })); describe('Expiration token', () => { test('should logout user', () => { jest.useFakeTimers(); // 用伪定时器下的Date.now()生成秒级时间戳,6秒后过期 const exp = Math.floor((Date.now() + 6000) / 1000); renderHook(() => useTokenExpirationCheck(exp)); expect(logout).not.toHaveBeenCalled(); // 快进6秒多一点,确保定时器触发 jest.advanceTimersByTime(6001); // 验证dispatch调用了logout expect(mockDispatch).toHaveBeenCalledWith(logout()); jest.useRealTimers(); }); });
可选:Hook本身优化
建议给useEffect加清理函数,避免组件卸载/exp变更时旧定时器触发导致bug:
useEffect(() => { if (exp) { const timeToLogout = exp * 1000 - Date.now(); const timer = setTimeout(() => { dispatch(logout()); }, timeToLogout); // 清理定时器 return () => clearTimeout(timer); } }, [exp, dispatch]);
内容的提问来源于stack exchange,提问作者littlesvensson
相关产品推荐
相关产品推荐

