You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 13:42:48