首页 > 解决方案 > 如何使用 react-testing-library 和 jest 来模拟自定义反应钩子更新?

问题描述

看看下面的自定义钩子。前提是它在query更改时更新其状态。

export function UseCustomHook() {
  const { query } = useRouter()
  const [state, setState] = useState({})

  useEffect(() => {
    const updatedState = helperFunction(query)
    setState(updatedState)
  }, [query])

  return state
}

目标是模拟useRouter,然后更新它以专门测试“当查询更新 n 次时,辅助函数被调用 n 次”

useRouter我们可以模拟模块

jest.mock('next/router', () => ({
  useRouter() {
    return {
      route: '/',
      pathname: '',
      query: {...},
      asPath: '',
    }
  },
}))

但这只是将其模拟为普通模块。我想将它模拟为一个钩子,然后在下面的测试中更新它

describe('useCustomHook', () => {
    it('should call helperFunction when query updates', () => {
        const query = {...}
        jest.spyOn(Router, 'useRouter' as any).mockImplementation(() => ({ query }))
        
        const { result } = renderHook(() => useCustomHook())
        expect(...)  
    })
  })
  

标签: reactjsunit-testingjestjsreact-hooksreact-hooks-testing-library

解决方案


您可以使用jest.mock()来模拟next/router模块、useRouter钩子及其返回值。在我们改变query值之后,我们应该调用rerender函数来重新渲染自定义的钩子,这样useEffect钩子就会使用新query的作为它的依赖。

此外,我使用jest.spyOn()添加 spy onconsole.count()方法来检查effect函数调用的次数。

例如

useCustomHook.ts

import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';

export function useCustomHook() {
  const { query } = useRouter();
  const [state, setState] = useState({});

  useEffect(() => {
    console.count('effect');
    setState({ id: query.id });
  }, [query]);

  return state;
}

useCustomHook.test.ts

import { useRouter } from 'next/router';
import { renderHook } from '@testing-library/react-hooks';
import { useCustomHook } from './useCustomHook';
import { mocked } from 'ts-jest/utils';
import { NextRouter } from 'next/dist/next-server/lib/router/router';

jest.mock('next/router');

const useMockRouter = mocked(useRouter);

describe('68660313', () => {
  test('should pass', () => {
    const countSpy = jest.spyOn(console, 'count');
    const query1 = ({ query: { id: '1' } } as unknown) as NextRouter;
    const query2 = ({ query: { id: '2' } } as unknown) as NextRouter;

    useMockRouter.mockReturnValue(query1);
    const { result, rerender } = renderHook(() => useCustomHook());
    expect(result.current).toEqual({ id: '1' });
    useMockRouter.mockReturnValue(query2);
    rerender();
    expect(result.current).toEqual({ id: '2' });
    expect(countSpy).toBeCalledTimes(2);
  });
});

测试结果:

 PASS  examples/68660313/useCustomHook.test.ts (7.664 s)
  68660313
    ✓ should pass (32 ms)

  console.count
    effect: 1

      at console.<anonymous> (node_modules/jest-environment-enzyme/node_modules/jest-mock/build/index.js:866:25)

  console.count
    effect: 2

      at console.<anonymous> (node_modules/jest-environment-enzyme/node_modules/jest-mock/build/index.js:866:25)

------------------|---------|----------|---------|---------|-------------------
File              | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
------------------|---------|----------|---------|---------|-------------------
All files         |     100 |      100 |     100 |     100 |                   
 useCustomHook.ts |     100 |      100 |     100 |     100 |                   
------------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        8.184 s

包的版本:

"next": "^11.0.1",
"jest": "^26.6.3",
"ts-jest": "^26.4.4",
"react": "^16.14.0",

推荐阅读