React Router页面跳转组件写法咨询及Jest+Enzyme测试建议
Is your current useHistory implementation reasonable?
It works functionally, but it’s not the most ideal choice for pure navigation scenarios. Here’s why:
- Semantics: Buttons are designed for triggering actions (like submitting forms or toggling UI), while links exist specifically for navigating between pages. Using a button for navigation breaks HTML’s semantic structure.
- Accessibility: Screen readers will announce this as a "button" instead of a "link", which can confuse users relying on assistive technology. Links also come with built-in keyboard behaviors (like being focusable with Tab and navigable with Enter) that you’d have to manually add to a button.
- Missing default behaviors: Right-clicking a button won’t give users options like "Open in new tab" or "Copy link address" unless you write custom code—features that links handle natively.
That said, if you had pre-navigation logic (like confirming a form submission or saving user data), using a button with useHistory would make perfect sense. But for your current use case (just navigating to the account page), there’s a better approach.
Should you use Link instead of button?
Absolutely! The Link component from react-router-dom is purpose-built for navigation and fixes all the issues mentioned above. Here’s how to refactor your component:
import React from 'react'; import { Link } from 'react-router-dom'; import './MyAccountButton.scss'; const MyAccountButton = ({ path }) => { return ( <div className="my-account-button"> <Link to={path} className="account-link"> My account </Link> </div> ); }; export default MyAccountButton;
You can style the .account-link class in your SCSS to look like a button if needed—just make sure to keep accessibility-friendly styles (like hover/focus states) intact.
How to test with Jest and Enzyme?
Let’s cover testing both versions of the component:
Testing the original useHistory version
First, mock the useHistory hook to simulate navigation behavior:
import React from 'react'; import { shallow } from 'enzyme'; import { useHistory } from 'react-router-dom'; import MyAccountButton from './MyAccountButton'; // Mock react-router's useHistory jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), useHistory: jest.fn(), })); describe('MyAccountButton', () => { it('navigates to the correct path when clicked', () => { const mockPush = jest.fn(); useHistory.mockReturnValue({ push: mockPush }); const testPath = '/user/account'; const wrapper = shallow(<MyAccountButton path={testPath} />); wrapper.find('button').simulate('click'); expect(mockPush).toHaveBeenCalledExactlyOnceWith(testPath); }); });
Testing the refactored Link version
For the Link component, wrap your component in MemoryRouter to provide routing context, then verify the link’s target and navigation behavior:
import React from 'react'; import { mount } from 'enzyme'; import { Link, MemoryRouter } from 'react-router-dom'; import MyAccountButton from './MyAccountButton'; describe('MyAccountButton (Link version)', () => { it('renders a Link with the correct target path', () => { const testPath = '/user/account'; const wrapper = mount( <MemoryRouter> <MyAccountButton path={testPath} /> </MemoryRouter> ); expect(wrapper.find(Link).prop('to')).toBe(testPath); }); it('navigates to the correct path when clicked', () => { const testPath = '/user/account'; const wrapper = mount( <MemoryRouter initialEntries={['/']}> <MyAccountButton path={testPath} /> </MemoryRouter> ); wrapper.find(Link).simulate('click'); expect(wrapper.instance().history.location.pathname).toBe(testPath); }); });
Note: We use mount instead of shallow here because we need the full rendering context of MemoryRouter to test navigation.
内容的提问来源于stack exchange,提问作者Kopeek

