【问题标题】:NextJS Router & Jest: How can I mock router change eventNextJS Router & Jest:如何模拟路由器更改事件
【发布时间】: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?
  • 你有没有想过这个问题?我想知道如何做同样的事情

标签: reactjs unit-testing mocking jestjs next.js


【解决方案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} />)
        
          });
        })
        

        【讨论】:

          猜你喜欢
          • 2021-11-06
          • 1970-01-01
          • 2022-12-21
          • 2021-06-03
          • 2016-02-26
          • 2021-03-17
          • 2020-07-14
          • 2016-12-17
          • 1970-01-01
          相关资源
          最近更新 更多