如何用Testing Library测试React Router链接并修复点击不跳转问题
我想要使用Testing Library测试React Router链接,目前已尝试如下实现:
App.js:
import React from 'react' import { Routes, NavLink, Route } from 'react-router-dom' function App() { return ( <div> <nav> <NavLink to={"/"}>Home</NavLink> <NavLink to={"/about"}>About</NavLink> </nav> <Routes> <Route path="/" element="home page" /> <Route path="/about" element="about page" /> </Routes> </div> ) } export default App
App.test.js:
import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { createMemoryHistory } from 'history'; import { Route, Router } from "react-router-dom" import App from './App'; test('first home link', () => { const history = createMemoryHistory({ initialEntries: ["/"] }); render( <Router location={history.location} navigator={history}> <App /> </Router> ); const linkElement = screen.getByRole("link", { name: /about/i }); userEvent.click(linkElement); screen.debug(); });
screen.debug输出结果:
<body> <div> <div> <nav> <a aria-current="page" class="active" href="/" > Home </a> <a class="" href="/about" > About </a> </nav> home page </div> </div> </body>
正如screen.debug的输出所示,点击About链接后页面并没有切换到about page,仍然显示home page内容。
请问该如何修复这个路由不跳转的问题?
项目依赖版本:
- "@testing-library/jest-dom": "^5.16.4"
- "@testing-library/react": "^13.3.0"
- "@testing-library/user-event": "^13.5.0"
- "history": "^5.3.0"
- "react": "^18.1.0"
- "react-router-dom": "^6.3.0"
- "react-dom": "^18.1.0"
修复方案
问题来自两个核心疏漏:
- React Router v6 的基础
Router组件不会主动监听传入的history实例变化,你直接传入history对象后,路由地址变更时没有触发React状态更新,组件不会重渲染。 @testing-library/user-event的所有交互方法都是异步API,没有加await等待操作执行完成的话,debug/断言逻辑会在路由跳转逻辑跑完之前就执行。
推荐写法(最简便)
测试场景直接用React Router内置的MemoryRouter即可,它内部已经封装了路由状态同步逻辑,不需要手动创建和维护history实例:
import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; // 替换基础Router为MemoryRouter import { MemoryRouter } from "react-router-dom" import App from './App'; // 测试用例改为async函数 test('navigate to about page when click about link', async () => { const user = userEvent.setup() render( // 直接通过initialEntries设置初始路由 <MemoryRouter initialEntries={["/"]}> <App /> </MemoryRouter> ); // 初始状态校验 expect(screen.getByText("home page")).toBeInTheDocument(); const linkElement = screen.getByRole("link", { name: /about/i }); // 等待点击交互完成 await user.click(linkElement); // 校验跳转结果 expect(screen.getByText("about page")).toBeInTheDocument(); screen.debug(); });
自定义history写法
如果必须手动创建history实例使用,需要自己封装路由状态同步逻辑,监听history变化触发组件重渲染:
import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { createMemoryHistory } from 'history'; import { Router } from "react-router-dom" import { useState, useLayoutEffect } from 'react'; import App from './App'; // 封装支持自定义history的路由组件 function HistoryRouter({history, ...props}) { const [routeState, setRouteState] = useState({ action: history.action, location: history.location }) useLayoutEffect(() => { // 监听路由变化,同步到React状态触发重渲染 const unlisten = history.listen(setRouteState) return unlisten }, [history]) return <Router {...props} location={routeState.location} navigationType={routeState.action} navigator={history} /> } test('navigate to about page when click about link', async () => { const history = createMemoryHistory({ initialEntries: ["/"] }); const user = userEvent.setup() render( <HistoryRouter history={history}> <App /> </HistoryRouter> ); expect(screen.getByText("home page")).toBeInTheDocument(); const linkElement = screen.getByRole("link", { name: /about/i }); await user.click(linkElement); expect(screen.getByText("about page")).toBeInTheDocument(); });
注:React Router v6.4以上版本已经内置了
HistoryRouter组件,不需要手动封装,但你当前使用的6.3.0版本无此内置能力,测试场景优先选择MemoryRouter成本最低。
内容的提问来源于stack exchange,提问作者Mehdi Saa
相关产品推荐
相关产品推荐

