Next.js 12.2.0升级后Jest模拟next/router致测试失败如何修复
在Jest + React Testing Library测试环境中,原有模拟next/router依赖的实现如下:
import * as nextRouter from 'next/router'; export const routerData = { pathname: '/users/create', route: '/users/create', query: { }, asPath: '/users/create', isFallback: false, basePath: '', isReady: true, isPreview: false, isLocaleDomain: false, events: {}, }; // mock router jest.mock('next/router'); nextRouter.useRouter.mockImplementation(() => (routerData)); describe('a component that requires next/router, () => ... );
升级到Next.js 12.2.0版本后,所有依赖该mock的测试全部失败,抛出如下提示:
No router instance found.
You should only use "next/router" on the client side of your app.
Next.js 12.2.0调整了next/router的内部校验逻辑:之前只要mockuseRouter的返回值就能正常运行,新版本会额外校验模块默认导出的Router实例是否存在,原写法只mock了useRouter方法,没有提供Router实例,自然过不了校验。另外新版本内部会调用events对象上的on/off/emit方法,原mock里events是空对象,就算过了实例校验也会报方法不存在的错。
方案1:补全模块mock(改动最小)
直接在jest.mock里补全所有必要的模块导出,包括默认导出的Router实例,同时把缺失的路由方法、events方法补成jest mock函数:
// 先补全routerData缺失的字段和方法 export const routerData = { pathname: '/users/create', route: '/users/create', query: { }, asPath: '/users/create', isFallback: false, basePath: '', isReady: true, isPreview: false, isLocaleDomain: false, // 补全events相关方法 events: { on: jest.fn(), off: jest.fn(), emit: jest.fn(), }, // 补全常用路由操作方法 push: jest.fn(() => Promise.resolve(true)), replace: jest.fn(() => Promise.resolve(true)), reload: jest.fn(), prefetch: jest.fn(() => Promise.resolve()), back: jest.fn(), beforePopState: jest.fn(), }; // 完整mock next/router模块,不要只mock useRouter jest.mock('next/router', () => ({ useRouter: () => routerData, default: routerData }));
这个方案改动量最小,和原有测试逻辑完全兼容,替换掉之前的mock代码就能解决报错。
方案2:通过RouterContext注入路由实例(长期推荐)
不用硬mock整个next/router模块,直接用Next内部的RouterContext上下文,在渲染测试组件的时候注入路由配置,走的和生产环境一样的上下文逻辑,后续Next版本升级就算改了模块导出结构也不容易失效:
import { render } from '@testing-library/react'; import { RouterContext } from 'next/dist/shared/lib/router-context.shared-runtime'; // 封装自定义渲染方法,自动注入路由上下文 const renderWithRouter = (ui, { routerConfig = routerData, ...renderOptions } = {}) => { return render( <RouterContext.Provider value={routerConfig}> {ui} </RouterContext.Provider>, renderOptions ) } // 测试用例中直接调用即可,不需要单独写jest.mock('next/router') test('渲染路由依赖组件', () => { renderWithRouter(<YourTestComponent />); // 后续正常写断言逻辑 })
用这个方案的时候也要注意,传入的routerConfig要包含前面提到的events方法和常用路由方法,避免运行时找不到方法报错。
内容的提问来源于stack exchange,提问作者Fer Toasted

