【问题标题】:Testing custom hook with SetTimeout and useEffect with Jest使用 SetTimeout 测试自定义钩子,使用 Jest 测试 useEffect
【发布时间】:2021-12-26 05:56:59
【问题描述】:

我正在尝试测试一个使用 useEffect 和 setTimeout 的相对简单的自定义挂钩。但是,我的测试失败了,我不知道出了什么问题。

这里是钩子本身(useTokenExpirationCheck.ts)

import { useEffect } from 'react';
import { logout } from '../features/profile/profileSlice';
import { useAppDispatch } from '../store/hooks';

export default function useTokenExpirationCheck(exp: number): void {
  const dispatch = useAppDispatch();
  useEffect(() => {
    if (exp) {
      const timeToLogout = exp * 1000 - Date.now();
      setTimeout(() => {
        dispatch(logout());
      }, timeToLogout);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [exp]);
}

和我的测试文件:

import { act, renderHook } from '@testing-library/react-hooks';
import useTokenExpirationCheck from './useTokenExpirationCheck';

jest.mock('../features/profile/profileSlice');
const logout = jest.fn();
const exp = Date.now() + 6000;

describe('Expiration token', () => {
  test('should logout user', async () => {
    jest.useFakeTimers();
    act(() => {
      renderHook(() => {
        useTokenExpirationCheck(exp);
      });
    });
    expect(logout).toHaveBeenCalledTimes(0);
    jest.advanceTimersByTime(60000);
    expect(logout).toHaveBeenCalledTimes(1);
  });
});

我所知道的是 exp 变量没有传递给 useTokenExpirationCheck 函数(console.log 在函数内部显示 0,当它被执行时)。所以基本上,我什至没有接触到 useEffect 本身......有什么想法会出错吗?

【问题讨论】:

    标签: reactjs typescript react-hooks jestjs react-hooks-testing-library


    【解决方案1】:

    这是我的测试策略:

    1. 我将使用redux-mock-store 创建一个模拟商店

    模拟存储将创建一个已调度的操作数组,用作测试的操作日志。

    这样我就可以通过store.getActions() 方法获取并断言调度的操作。

    1. 我将使用模拟返回值模拟Date.now() 方法,以便测试不再依赖系统日期。不同时区和不同 CI/CD 服务器的系统日期可能不同。

    2. 使用Props更新输入(useEffect的deps)并重新渲染钩子,以便我们可以测试案例:如果exp被更改

    useTokenExpirationCheck.ts:

    import { useEffect } from 'react';
    import { useDispatch } from 'react-redux';
    
    const logout = () => ({ type: 'LOGOUT' });
    
    export default function useTokenExpirationCheck(exp: number): void {
      const dispatch = useDispatch();
      useEffect(() => {
        if (exp) {
          console.log('exp: ', exp);
          const timeToLogout = exp * 1000 - Date.now();
          setTimeout(() => {
            dispatch(logout());
          }, timeToLogout);
        }
      }, [exp]);
    }
    

    useTokenExpirationCheck.test.tsx:

    import { renderHook } from '@testing-library/react-hooks';
    import React from 'react';
    import { Provider } from 'react-redux';
    import createMockStore from 'redux-mock-store';
    import useTokenExpirationCheck from './useTokenExpirationCheck';
    
    describe('useTokenExpirationCheck', () => {
      test('should dispatch logout action after delay', async () => {
        let exp = 6000;
        jest.spyOn(Date, 'now').mockReturnValue(5900 * 1000);
        const mockStore = createMockStore([]);
        const store = mockStore({});
        jest.useFakeTimers();
        const { rerender } = renderHook(() => useTokenExpirationCheck(exp), {
          wrapper: ({ children }) => <Provider store={store}>{children}</Provider>,
        });
    
        jest.advanceTimersByTime(100 * 1000);
        expect(store.getActions()).toEqual([{ type: 'LOGOUT' }]);
    
        exp = 6100;
        rerender();
        jest.advanceTimersByTime(200 * 1000);
        expect(store.getActions()).toEqual([{ type: 'LOGOUT' }, { type: 'LOGOUT' }]);
      });
    });
    

    测试结果:

     PASS  examples/69967414/useTokenExpirationCheck.test.tsx (8.329 s)
      useTokenExpirationCheck
        ✓ should pass (39 ms)
    
      console.log
        exp:  6000
    
          at examples/69967414/useTokenExpirationCheck.ts:10:15
    
      console.log
        exp:  6100
    
          at examples/69967414/useTokenExpirationCheck.ts:10:15
    
    ------------------------|---------|----------|---------|---------|-------------------
    File                    | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
    ------------------------|---------|----------|---------|---------|-------------------
    All files               |     100 |       50 |     100 |     100 |                   
     ...nExpirationCheck.ts |     100 |       50 |     100 |     100 | 9                 
    ------------------------|---------|----------|---------|---------|-------------------
    Test Suites: 1 passed, 1 total
    Tests:       1 passed, 1 total
    Snapshots:   0 total
    Time:        8.885 s, estimated 10 s
    Ran all test suites related to changed files.
    

    【讨论】:

    • slideshowp2,这太棒了!我使用的是 Redux 工具包而不是 redux,所以我将类型更改为配置文件/注销,(按照工具包切片工作流程),但基本上我根据您的建议进行了更改,它终于可以工作了!我花在“测试”这件作品上的时间比花在钩子本身上的时间要多得多……谢谢阿根♥
    猜你喜欢
    • 1970-01-01
    • 2022-01-13
    • 2020-01-26
    • 2020-02-19
    • 2021-06-07
    • 2020-09-14
    • 2020-03-20
    • 2021-03-10
    • 1970-01-01
    相关资源
    最近更新 更多