1

我有一个自定义钩子,它在 URL 参数更改时调度一个动作:

export const useUser = (): void => {
  const dispatch = useDispatch();

  const { user } = useParams<{ user: string }>();

  useEffect(() => {dispatch(getUser(user));
  }, [dispatch, user]);
};

我有一个模拟useDispatch和的测试useParams

const dispatch = jest.fn();

const useDispatchSpy = jest.spyOn(reactRedux, 'useDispatch').mockImplementation(() => {
  return dispatch;
});

const useParamsSpy = jest.spyOn(reactRouter, 'useParams').mockImplementation(() => {
  return { user: 'test' };
});

const getUserSpy = jest.spyOn(slice, 'getUser');

renderHook(() => useRequestProfile());

expect(getUserSpy.mock.calls).toEqual([['test']]);

现在,这行得通,因为我可以确定它getUser('test')已被调用,但包装它的useEffect目的是确保它只被调用一次。我该如何测试呢?

4

2 回答 2

0

使用toHaveBeenCalledTimes匹配器。

expect(getUserSpy).toHaveBeenCalledTimes(1);
于 2022-01-31T16:06:22.173 回答
0

您可以将useUser钩子放在组件内。将一些道具传递给这个组件并检查。稍后,传递新的 props 以重新渲染组件并再次检查是否getUserSpy被多次调用。

此外,我们使用MemoryRouterwith initial entries 而不是 mockinguseParams来提供 URL 参数。

我们测试的预期结果是:无论组件渲染多少次,由于dispatchuser参数是稳定的,钩子中的useEffectinuseUser只会执行一次。

例如

useUser.ts

import { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { useParams } from 'react-router-dom';
import * as slice from './slice';

export const useUser = (): void => {
  const dispatch = useDispatch();

  const { user } = useParams<{ user: string }>();
  useEffect(() => {
    dispatch(slice.getUser(user));
  }, [dispatch, user]);
};

slice.ts

export const getUser = (user) => ({ type: 'GET_USER', payload: user });

useUser.test.tsx

import { render } from '@testing-library/react';
import { renderHook } from '@testing-library/react-hooks';
import React from 'react';
import { Provider } from 'react-redux';
import { MemoryRouter, Route } from 'react-router-dom';
import { createStore } from 'redux';
import * as slice from './slice';
import { useUser } from './useUser';

describe('useUser', () => {
  test('should pass', () => {
    const getUserSpy = jest.spyOn(slice, 'getUser');
    function TestComp(props) {
      console.log(props);
      useUser();
      return null;
    }
    const store = createStore(() => ({}));

    const { rerender } = renderHook(TestComp, {
      initialProps: { data: '1' },
      wrapper: ({ children }) => (
        <Provider store={store}>
          <MemoryRouter initialEntries={['/test']}>
            <Route path="/:user">{children}</Route>
          </MemoryRouter>
        </Provider>
      ),
    });
    expect(getUserSpy).toBeCalledWith('test');
    rerender({ data: '2' });
    expect(getUserSpy).toBeCalledTimes(1);
  });
});

测试结果:

 PASS  stackoverflow/70928091/useUser.test.tsx (7.748 s)
  useUser
    ✓ should pass (39 ms)

  console.log
    { data: '1' }

      at TestComp (stackoverflow/70928091/useUser.test.tsx:14:15)

  console.log
    { data: '2' }

      at TestComp (stackoverflow/70928091/useUser.test.tsx:14:15)

------------|---------|----------|---------|---------|-------------------
File        | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
------------|---------|----------|---------|---------|-------------------
All files   |     100 |      100 |     100 |     100 |                   
 slice.ts   |     100 |      100 |     100 |     100 |                   
 useUser.ts |     100 |      100 |     100 |     100 |                   
------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        8.253 s, estimated 9 s
于 2022-02-07T05:57:57.270 回答