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

IT技术 reactjs unit-testing mocking jestjs next.js
2021-05-15 14:59:19

我正在使用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事件吗?谢谢

4个回答

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

代码取自路由器实现

在我的实现中使用useRouter钩子next/router

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');
});

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

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

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

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

对于任何坚持在 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();
});