如何使用renderHook测试含React Router导航逻辑的Hooks?
如何在renderHook中测试依赖React Router导航的Hook?
我在使用renderHook工具测试带有React Router导航逻辑的Hooks时遇到了问题——无法在测试中正确模拟导航操作,导致无法验证路由变化时Hook的状态更新。
示例Hook
我要测试的Hook逻辑如下,它根据当前路由判断导航栏是否可展开:
export const useNavBar = () => { const location = useLocation(); const isExpandable = useMemo(() => { // 注意:原代码里的pathName是拼写错误,应该是pathname(小写n) const aPath = matchPath({path: "/a-route"}, location.pathname); const anotherPath = matchPath({path: "/another-route"}, location.pathname); return !aPath && !anotherPath }, [location.pathname]); return { isExpandable } }
测试遇到的问题
我尝试用window.history.pushState模拟路由跳转,但发现Hook不会触发重渲染,isExpandable的值始终不变;手动调用rerender也没用,因为useLocation无法感知到直接修改window.history带来的变化。测试代码如下:
it("changes expandable accessing an invalid route", () => { const { result } = renderHook(useNavBar, { wrapper: BrowserRouter }); expect(result.current.isExpandable).toBeTruthy(); window.history.pushState({}, '', '/a-route'); expect(result.current.isExpandable).toBeFalsy(); });
解决方案
直接操作window.history不会触发React Router内部的上下文更新,必须通过React Router提供的API或测试方式来模拟路由变化,这里提供两种可行方案:
方案1:用MemoryRouter配合rerender更新路由
创建一个可配置路由条目的Wrapper组件,通过rerender更新Wrapper的initialEntries来模拟路由变化:
// 定义可配置的Router包装组件 const RouterWrapper = ({ children, entries = ['/'] }) => { return <MemoryRouter initialEntries={entries}>{children}</MemoryRouter>; }; // 测试代码 it("changes expandable when accessing a non-expandable route", () => { // 初始路由为根路径 const { result, rerender } = renderHook(useNavBar, { wrapper: (props) => <RouterWrapper entries={['/']}>{props.children}</RouterWrapper>, }); expect(result.current.isExpandable).toBeTruthy(); // 重新渲染时传入新路由,触发useLocation更新 rerender({ wrapper: (props) => <RouterWrapper entries={['/a-route']}>{props.children}</RouterWrapper>, }); expect(result.current.isExpandable).toBeFalsy(); });
方案2:通过useNavigate触发导航
在自定义Wrapper中暴露useNavigate函数,测试时直接调用该函数触发路由变化,再用waitFor等待状态更新:
// 定义Wrapper并暴露navigate函数 let navigateRef; const RouterWrapper = ({ children }) => { const navigate = useNavigate(); navigateRef = navigate; return <BrowserRouter>{children}</BrowserRouter>; }; // 测试代码 it("changes expandable when accessing a non-expandable route", async () => { const { result } = renderHook(useNavBar, { wrapper: RouterWrapper }); expect(result.current.isExpandable).toBeTruthy(); // 调用navigate触发路由跳转,React Router会自动更新上下文 navigateRef('/a-route'); // 等待Hook重渲染完成后断言 await waitFor(() => { expect(result.current.isExpandable).toBeFalsy(); }); });
注意事项
- 原Hook中存在拼写错误:
location.pathName应为location.pathname(小写字母n),这个错误会导致路由匹配失效,需要先修正。 - 使用
waitFor是因为路由变化触发的重渲染是异步的,直接断言会拿到旧值。
内容的提问来源于stack exchange,提问作者Rumpelstinsk
相关产品推荐
相关产品推荐

