使用React Router v6的组件Jest快照测试运行失败求助
React Router v6 + Jest快照测试崩溃问题解决
问题场景
使用Jest运行快照测试时,所有用到react-router-dom组件(比如Link)的组件均崩溃,无法生成快照。
相关代码
header.tsx
import React from 'react'; import logo from '../../logo.svg' import 'antd/dist/antd.min.css'; import { Link } from "react-router-dom"; import { PageHeader, Menu, Avatar, Button, Dropdown, Typography } from 'antd'; import { BellOutlined, UserOutlined } from '@ant-design/icons'; const { Title } = Typography; const profileMenu = ( <Menu items={[]} /> ); function WebHeader() { return ( <PageHeader ghost={false} avatar={{ src: <img src={logo} alt='icon' />, size: 48 }} title={<Title level={1} style={{ marginBottom: 0 }}>SciOps</Title>} subTitle={ <Menu mode="horizontal" defaultSelectedKeys={['projects']}> <Menu.Item key="projects"> <Link to='/projects'>Projects</Link> </Menu.Item> <Menu.Item key="orgs"> <Link to='/organizations'>Organizations</Link> </Menu.Item> </Menu> } extra={ <> <Button shape='circle' icon={<BellOutlined />} /> <Dropdown overlay={profileMenu} placement="bottom" arrow={{ pointAtCenter: true }} trigger={['click']}> <a href="/#" onClick={e => e.preventDefault()}> <Avatar size={36} icon={<UserOutlined />} style={{ marginRight: 0 }} /> </a> </Dropdown> </> } ></PageHeader> ) } export default WebHeader
header.test.tsx(原测试代码)
import React from 'react'; import renderer from 'react-test-renderer' import WebHeader from '../../components/header/header' it("Snapshot of Header", () => { const tree = renderer.create(<WebHeader />).toJSON(); expect(tree).toMatchSnapshot(); })
错误信息(翻译后)
src/_test_/components/header.test.tsx ● 控制台输出 console.error 上述错误发生在<Link>组件中: at LinkWithRef (/home/adib/forks/sciops-hub/packages/react-router-dom/index.tsx:264:7) at span at li at DomWrapper (/home/adib/forks/sciops-hub/front-end/node_modules/rc-resize-observer/lib/SingleObserver/DomWrapper.js:31:34) at SingleObserver (/home/adib/forks/sciops-hub/front-end/node_modules/rc-resize-observer/lib/SingleObserver/index.js:27:24) at ResizeObserver (/home/adib/forks/sciops-hub/front-end/node_modules/rc-resize-observer/lib/index.js:27:24) at InternalItem (/home/adib/forks/sciops-hub/front-end/node_modules/rc-overflow/lib/Item.js:29:25) at InternalRawItem (/home/adib/forks/sciops-hub/front-end/node_modules/rc-overflow/lib/RawItem.js:29:23) at LegacyMenuItem (/home/adib/forks/sciops-hub/front-end/node_modules/rc-menu/lib/MenuItem.js:71:34) at InternalMenuItem (/home/adib/forks/sciops-hub/front-end/node_modules/rc-menu/lib/MenuItem.js:102:21) at MenuItem (/home/adib/forks/sciops-hub/front-end/node_modules/rc-menu/lib/MenuItem.js:232:24) at Trigger (/home/adib/forks/sciops-hub/front-end/node_modules/rc-trigger/lib/index.js:80:36) at Tooltip (/home/adib/forks/sciops-hub/front-end/node_modules/rc-tooltip/lib/Tooltip.js:29:32) at /home/adib/forks/sciops-hub/front-end/node_modules/antd/lib/tooltip/index.js:103:33) at MenuItem (/home/adib/forks/sciops-hub/front-end/node_modules/antd/lib/menu/MenuItem.js:61:37) at InheritableContextProvider (/home/adib/forks/sciops-hub/front-end/node_modules/rc-menu/lib/context/MenuContext.js:40:23) at ul at DomWrapper (/home/adib/forks/sciops-hub/front-end/node_modules/rc-resize-observer/lib/SingleObserver/DomWrapper.js:31:34) at SingleObserver (/home/adib/forks/sciops-hub/front-end/node_modules/rc-resize-observer/lib/SingleObserver/index.js:27:24) at ResizeObserver (/home/adib/forks/sciops-hub/front-end/node_modules/rc-resize-observer/lib/index.js:27:24) at Overflow (/home/adib/forks/sciops-hub/front-end/node_modules/rc-overflow/lib/Overflow.js:45:32) at InheritableContextProvider (/home/adib/forks/sciops-hub/front-end/node_modules/rc-menu/lib/context/MenuContext.js:40:23) at /home/adib/forks/sciops-hub/front-end/node_modules/rc-menu/lib/Menu.js:79:32) at /home/adib/forks/sciops-hub/front-end/node_modules/antd/lib/menu/index.js:72:24) at Menu (/home/adib/forks/sciops-hub/front-end/node_modules/antd/lib/menu/index.js:191:37) at span at div at div at div at DomWrapper (/home/adib/forks/sciops-hub/front-end/node_modules/rc-resize-observer/lib/SingleObserver/DomWrapper.js:31:34) at SingleObserver (/home/adib/forks/sciops-hub/front-end/node_modules/rc-resize-observer/lib/SingleObserver/index.js:27:24) at ResizeObserver (/home/adib/forks/sciops-hub/front-end/node_modules/rc-resize-observer/lib/index.js:27:24) at PageHeader (/home/adib/forks/sciops-hub/front-end/node_modules/antd/lib/page-header/index.js:129:45) at WebHeader 考虑在组件树中添加错误边界来自定义错误处理行为。 at logCapturedError (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:8661:23) at update.callback (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:8694:5) at callCallback (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:4092:12) at commitUpdateQueue (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:4113:9) at commitLayoutEffectOnFiber (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:13232:13) at commitLayoutMountEffects_complete (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:14330:9) at commitLayoutEffects_begin (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:14316:7) at commitLayoutEffects (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:14301:3)
问题原因
React Router的Link组件依赖Router提供的上下文(Context)才能正常工作,直接在测试中渲染包含Link的组件时,没有任何Router组件提供上下文,导致Link抛出错误。
解决方案
方案1:用MemoryRouter包裹组件
在测试中导入React Router的MemoryRouter,将WebHeader组件包裹在其中,提供路由上下文:
import React from 'react'; import renderer from 'react-test-renderer' import { MemoryRouter } from 'react-router-dom'; // 导入MemoryRouter import WebHeader from '../../components/header/header' it("Snapshot of Header", () => { const tree = renderer.create( <MemoryRouter> {/* 包裹组件 */} <WebHeader /> </MemoryRouter> ).toJSON(); expect(tree).toMatchSnapshot(); })
方案2:Mock Link组件
如果不需要测试Link的路由功能,可以直接Mock react-router-dom的Link组件,让它渲染成普通的HTML标签(比如a标签或span):
import React from 'react'; import renderer from 'react-test-renderer' import WebHeader from '../../components/header/header' // Mock Link组件 jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), Link: ({ children, to }) => <a href={to}>{children}</a>, })); it("Snapshot of Header", () => { const tree = renderer.create(<WebHeader />).toJSON(); expect(tree).toMatchSnapshot(); })
两种方案都能解决测试崩溃的问题,方案1更贴近真实环境,方案2则简化测试,专注于组件UI结构。
内容的提问来源于stack exchange,提问作者Adib Baji
相关产品推荐
相关产品推荐

