0

我正在尝试测试一个使用 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 本身......有什么想法会出错吗?

4

1 回答 1

1

这是我的测试策略:

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

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

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

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

  2. 使用Props更新输入(deps of useEffect)并重新渲染钩子,以便我们可以测试案例:If expis changed

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.
于 2021-11-15T03:35:23.070 回答