测试React Router组件时遇useLoaderData报错问题求助
问题:Vitest同时运行两个React Router组件测试时抛出
useLoaderData must be used within a data router错误 - 单独运行任意一个测试均正常通过,同时运行则触发上述错误
- 涉及组件:
Timestamp(依赖useLoaderData)、DateError(依赖useRouteError) - 生产环境使用React Router v6+的
createBrowserRouter(数据路由),测试中用了MemoryRouter包裹组件
错误原因
vi.mock使用方式错误:将vi.mock写在测试函数内部,Vitest的模块缓存机制会导致两个测试的mock冲突,后运行的测试无法正确覆盖前一个的mock配置。- 测试路由环境不匹配:
useLoaderData、useRouteError是数据路由专属钩子,仅能在RouterProvider(而非传统的MemoryRouter)提供的上下文内使用,即使mock了钩子函数,React Router的内部校验仍会检测到环境不匹配并报错。
解决步骤及修改后的代码
1. 调整mock策略
将react-router-dom的mock移到测试文件顶部,全局统一mock,再在每个测试中单独设置mock返回值,避免缓存冲突。
2. 替换测试路由环境
用createMemoryRouter创建测试用数据路由,通过RouterProvider提供上下文,匹配生产环境的路由模式。
修改后的完整测试代码
import { cleanup, render, screen } from '@testing-library/react'; import { vi } from 'vitest'; import { createMemoryRouter, RouterProvider } from 'react-router-dom'; import Timestamp from './Timestamp'; import DateError from './DateError'; // 全局mock react-router-dom,只mock需要的钩子 vi.mock("react-router-dom", async () => { const originalModule = await vi.importActual("react-router-dom"); return { ...originalModule, useLoaderData: vi.fn(), useRouteError: vi.fn(), } }); // 每个测试后清理React上下文并重置mock afterEach(() => { cleanup(); vi.clearAllMocks(); }); test('Verify that <Timestamp /> displays both the UNIX and UTC dates', () => { // 设置当前测试的useLoaderData返回值 const mockDateData = {unix: 7728271946, utc: "Tue, 31 Mar 1970 10:44:31 GMT"}; vi.mocked(vi.importActual("react-router-dom").useLoaderData).mockReturnValue(mockDateData); // 创建测试用内存数据路由 const router = createMemoryRouter([ { path: "/:date", element: <Timestamp />, }, ], { initialEntries: ['/123456'], // 传入合法的date参数占位 }); render(<RouterProvider router={router} />); const timestamps = screen.getAllByRole("paragraph"); expect(timestamps[0]).toHaveTextContent("Date in UNIX: 7728271946"); expect(timestamps[1]).toHaveTextContent("Date in UTC: Tue, 31 Mar 1970 10:44:31 GMT"); }); test('Verify that DateError displays invalid date message', () => { // 设置当前测试的useRouteError返回值 const mockErrorMessage = "282347223489234 is an invalid date!"; vi.mocked(vi.importActual("react-router-dom").useRouteError).mockReturnValue(mockErrorMessage); // 创建包含errorElement的测试路由,模拟loader抛出错误 const router = createMemoryRouter([ { path: "/:date", element: <Timestamp />, errorElement: <DateError />, loader: () => { throw new Error(mockErrorMessage); }, }, ], { initialEntries: ['/282347223489234'], }); render(<RouterProvider router={router} />); const message = screen.getByRole("paragraph"); expect(message).toHaveTextContent("282347223489234 is an invalid date!"); });
关键改动说明
- 全局mock+测试内重置:避免多次mock导致的模块缓存问题,确保每个测试的mock独立。
- 使用
RouterProvider:匹配生产环境的 data router 上下文,解决钩子的环境校验问题。 - 模拟真实路由结构:用
createMemoryRouter配置测试路由,能正确触发loader和errorElement的逻辑,测试更贴近真实场景。
内容的提问来源于stack exchange,提问作者0binny0
相关产品推荐
相关产品推荐

