NextJS Router & Jest:如何模拟路由器更改事件

Posted

技术标签:

【中文标题】NextJS Router & Jest:如何模拟路由器更改事件【英文标题】:NextJS Router & Jest: How can I mock router change event 【发布时间】:2020-05-14 15:01:01 【问题描述】:

我正在使用 next-routes 并且在我的 React 组件中我使用下面的 useEffect 代码块来检测 Router 更改事件:

useEffect(() => 
  // THIS BLOCK WILL BE EXECUTED WHEN THE COMPONENT IS ALREADY MOUNTED AND SCREEN WOULD BE REDNDERED IN WHEN ROUTED CLIENT SIDE
  const handleRouterChangeComplete = url => 
    // console.log('INNNN handleRouterChangeComplete url = ', url);
    // MY REST OF THE LOGIC HERE
  ;

  // THIS BLOCK WILL BE EXECUTED WHEN THE SCREEN WOULD BE REDNDERED IN WHEN ROUTED CLIENT SIDE
  Router.events.on('routeChangeComplete', handleRouterChangeComplete);
  return () => 
    Router.events.off('routeChangeComplete', handleRouterChangeComplete);
  ;
, []);

我正在使用jest 为该组件编写单元测试用例并面临以下错误:

TypeError: Cannot read property 'on' of undefined

      183 |
      184 |     // THIS BLOCK WILL BE EXECUTED WHEN THE SCREEN WOULD BE REDNDERED IN WHEN ROUTED CLIENT SIDE
    > 185 |     Router.events.on('routeChangeComplete', handleRouterChangeComplete);

有人可以帮我模拟Router Change 事件吗? 谢谢

【问题讨论】:

模拟Router的事件。 是的,但我该怎么做。任何代码sn-p? 你有没有想过这个问题?我想知道如何做同样的事情 【参考方案1】:

您可以使用Router.events.emit('event-name')触发相关的更改事件;

代码取自router implementation。

【讨论】:

对不起,伙计。但我不知道如何在我的开玩笑单元测试用例中使用它。【参考方案2】:

在这个 github 问题上找到了方法

只需将回调存储到一个变量中,这样您就可以随时“触发”事件。

let routeChangeComplete;
Router.default.events.on = jest.fn((event, callback) => 
    routeChangeComplete = callback;
  );

https://github.com/vercel/next.js/issues/7586

【讨论】:

【参考方案3】:

我在我的实现中使用来自next/routeruseRouter 钩子。

import  FunctionComponent, useEffect  from 'react';
import  useRouter  from 'next/router';

const handleRouteChange = (url) => 
  // handle route change
  return `handling url - $url`
;

const Component: FunctionComponent = () => 
  const router = useRouter();

  useEffect(() => 
    router.events.on('routeChangeComplete', handleRouteChange);
    return () => 
      router.events.off('routeChangeComplete', handleRouteChange);
    ;
  , [router.events]);

  // do other things
;

并在我的测试中执行以下操作来模拟 useRouter

jest.mock('next/router');

let eventName;
let routeChangeHandler;

useRouter.mockImplementation(() => 
  return 
    events: 
      on: jest.fn((event, callback) => 
        eventName = event;
        routeChangeHandler = callback;
      ),
      off: jest.fn((event, callback) => 
        eventName = event;
        routeChangeHandler = callback;
      ),
    ,
  ;
);

我的测试看起来像

it('should call the required functions', () => 
    render(<Component />);

    expect(useRouter).toHaveBeenCalledTimes(1);
    expect(eventName).toBe('routeChangeComplete'); 
    expect(routeChangeHandler).toBeDefined();

    expect(routeChangeHandler('/')).toBe('handling url - /');

    useRouter().events.on('onEvent', routeChangeHandler);
    expect(useRouter).toHaveBeenCalledTimes(2);
    expect(eventName).toBe('onEvent');

    useRouter().events.off('offEvent', routeChangeHandler);
    expect(useRouter).toHaveBeenCalledTimes(3);
    expect(eventName).toBe('offEvent');
);

【讨论】:

【参考方案4】:

对于在 nextjs 中测试路由器更改事件的人来说,这是一个示例。

简答

您可以使用Router.events.emit 从您的测试中发出事件,然后断言您期望被触发的回调。

长答案

以下是我在示例中使用的场景,但是您可以根据需要对其进行修改。

场景:在我的主应用程序中,我使用NProgress 来显示每个路线更改的进度条。自定义应用如下所示

import Router from 'next/router';
import NProgress from 'nprogress';

Router.events.on('routeChangeStart', () => NProgress.start());
Router.events.on('routeChangeComplete', () => NProgress.done());
Router.events.on('routeChangeError', () => NProgress.done());

export default CustomApp() 
   ...


现在,在我的测试中,我想测试回调是否被正确调用。开玩笑的测试是这样的:

import Router from 'next/router;
import NProgress from 'nprogress;
import  render  from '@testing-library/react';
import CustomApp from 'pages/_app';

const mockStart = jest.spyOn(NProgress, 'start');
const mockDone = jest.spyOn(NProgress, 'done');

it('starts nprogress on router change start', () => 
  render(<CustomApp />)
  Router.events.emit('routeChangeStart');
  expect(mockStart).toHaveBeenCalled();
);

it('ends nprogress on router change complete', () => 
  render(<CustomApp />)
  Router.events.emit('routeChangeComplete');
  expect(mockDone).toHaveBeenCalled();
);

【讨论】:

【参考方案5】:

我的答案基于这里的冗长线程:https://github.com/vercel/next.js/issues/7479。

希望有人可以通过说明它的工作原理或改进最佳实践来做出更好的答案。

// _app.test.js
/**
 * @jest-environment jsdom
 */

import React from 'react';
import  render  from '@testing-library/react';
import MyComponentThatUsesRouter from '../components/MyComponentThatUsesRouter';

//Router mock
const useRouter = jest.spyOn(require("next/router"), "useRouter");

describe('MyComponentThatUsesRouter', () => 
  it('renders without crashing', () => 
    // hypothetical prop possibly not needed
    const myprop1=red, blue, orange
    // Specific mock default values - change as needed
    useRouter.mockImplementationOnce(() => (
        basePath: '/',
        pathname: '/',
        route: '/',
        query: ,
        asPath: '/',
        push: jest.fn(() => Promise.resolve(true)),
        replace: jest.fn(() => Promise.resolve(true)),
        reload: jest.fn(() => Promise.resolve(true)),
        prefetch: jest.fn(() => Promise.resolve()),
        back: jest.fn(() => Promise.resolve(true)),
        beforePopState: jest.fn(() => Promise.resolve(true)),
        isFallback: false,
        events: 
          on: jest.fn(),
          off: jest.fn(),
          emit: jest.fn(),
        ,
    ));
    // Your component that is using Router
    render(<MyComponentThatUsesRouter prop1=myprop1 />)

  );
)

【讨论】:

以上是关于NextJS Router & Jest:如何模拟路由器更改事件的主要内容,如果未能解决你的问题,请参考以下文章

在 TypeScript 中使用 NextJS 设置 Jest + React 测试库 -- 设置 upp jest.config.js 时出错

用 Jest 测试 NextJS API 中间件

Jest 不会在 NextJS 应用程序中解析 SVG 图片

将 React/Enzyme/Jest 与 Nextjs 一起使用时是不是可以获得整页 html?

如何使用 NextJS API 在 Jest 单元测试中考虑 Google reCaptcha

Jest and React and Typescript & React-Testing-Library 错误