使用Jest模拟NextJS router事件时报Cannot read property 'on' of undefined如何解决
问题原因及解决方案
核心问题
- mock 时机错误:你在
it测试块中才定义useRouter的 mock 实现,此时组件已经在beforeEach阶段完成挂载,组件内部useEffect执行时调用的useRouter还没有返回正确的结构,events属性为undefined,自然访问on方法时报错。 - mock 实例未绑定:你在测试块中单独声明的
let useRouter = jest.fn()是孤立的 mock 函数,和next/router模块导出的useRouter没有任何关联,mock 规则完全没有生效。
修改方案
首先在测试文件顶部绑定 next/router 的 mock 规则,再在组件挂载前提前配置 useRouter 的返回值,确保组件渲染时就能拿到符合预期的 router 结构。
修改后的测试代码示例:
import { mount, ReactWrapper } from 'enzyme'; import FavoritesPage from 'pages/user/favorites'; import configureStore, { MockStore } from 'redux-mock-store'; import storeInitialState from '__tests__/unit/support/storeInitialState'; import { Provider } from 'react-redux'; import { waitFor } from '@testing-library/react'; import { RequestStates } from 'types/State'; import useAdSetup from 'lib/hooks/useAdSetup'; // 新增导入next/router的useRouter import { useRouter } from 'next/router'; const mockRequestState = RequestStates.Finished; jest.mock('lib/Auth'); jest.mock('lib/EventLogger'); jest.mock('lib/hooks/useAdSetup'); jest.mock('lib/hooks/useFetchFavorites', () => { return (): { requestState: RequestStates } => { return { requestState: mockRequestState, }; }; }); // 修改next/router的mock规则,绑定useRouter为mock函数 jest.mock('next/router', () => ({ useRouter: jest.fn() })); const mockStore = configureStore(); let store: MockStore; describe('when clicking the price drop subnav button', () => { let component: ReactWrapper; let eventName; let routeChangeHandler; beforeEach(async () => { store = mockStore(storeInitialState); // 组件挂载前提前配置useRouter的返回值 useRouter.mockImplementation(() => { return { events: { on: jest.fn((event, callback) => { eventName = event; routeChangeHandler = callback; }), off: jest.fn((event, callback) => { eventName = event; routeChangeHandler = callback; }), // 如果组件用到其他router属性(如pathname、query)可以在此补充 }, }; }); await waitFor(() => { component = mount( <Provider store={store}> <FavoritesPage /> </Provider> ); }); component.find('.price-drop-nav-item').simulate('click'); }); it('shows price drops', () => { expect(useRouter).toHaveBeenCalledTimes(1); expect(component.find('.price-drop-nav-item').hasClass('active')).toBeTruthy(); }); });
如果你的组件是使用withRouter高阶组件注入的router而非useRouter Hook,需要调整next/router的mock规则为:
jest.mock('next/router', () => ({ useRouter: jest.fn(), withRouter: (Component) => (props) => <Component {...props} router={{ events: { on: jest.fn((event, callback) => { eventName = event; routeChangeHandler = callback; }, off: jest.fn((event, callback) => { eventName = event; routeChangeHandler = callback; }), } }} /> }));
内容的提问来源于stack exchange,提问作者Igor Shmukler
相关产品推荐
相关产品推荐

