如何使用 jest.mock 模拟 useRef 和反应测试库

Posted

技术标签:

【中文标题】如何使用 jest.mock 模拟 useRef 和反应测试库【英文标题】:How to mock useRef using jest.mock and react testing library 【发布时间】:2021-05-25 17:03:28 【问题描述】:

我有一个测试用例,我需要模拟 useRef 以返回模拟当前值。我试过 jest.mock 但它返回一个 htmlDivElement 。

代码:

   const ref = useRef<HTMLDivElement | null>(null);

测试:

  jest.mock('react', () => 
     const originReact = jest.requireActual('react');
       return 
          ...originReact,
          useRef: jest.fn(),
       ;
  );



  React.useRef.mockReturnValue( current: offsetWith: 100 ); 

模拟回报

[  type: 'return', value:  current: [HTMLDivElement]   ]

【问题讨论】:

不要模拟任何的 React API,你不拥有它。测试组件的行为,而不是实现 我明白了。有没有办法测试当前参考值(如 offsetWidth)是否发生变化。谢谢 【参考方案1】:

@jonrsharpe 的建议是组件测试的一般原则,但你的问题是一个特例,涉及到 DOM 的一些属性和方法,例如offsetWidthgetBoundingClientRect() 方法。 jsdom的运行环境无法返回,浏览器运行环境下的渲染引擎返回结果导致offsetWidthgetBoundingClientRect()方法返回的属性值始终为0

所以这里我们需要模拟ref

这里的模拟ref也很特别。除了使用jest.mock() 进行部分模拟外,ref.current 属性将在组件挂载后由react 分配。为了拦截这个操作,我们需要创建一个带有current对象属性的模拟ref对象,使用Object.defineProperty()方法为这个ref.current属性定义gettersetter,并拦截属性任务。

jest.spyOn 方法采用 accessType 的第三个可选参数,可以是 'get' 或 'set',当您想分别监视 getter 或 setter 时,这被证明是有用的。

例如

index.tsx:

import React,  useEffect, useRef  from 'react';

export default function App() 
  const ref = useRef<HTMLDivElement>(null);
  useEffect(() => 
    console.log(ref.current?.offsetWidth);
  , [ref]);
  return <div ref=ref>app</div>;

index.test.tsx:

import  render  from '@testing-library/react';
import React,  useRef  from 'react';
import  mocked  from 'ts-jest/utils';
import App from './';

jest.mock('react', () => 
  return 
    ...jest.requireActual<typeof React>('react'),
    useRef: jest.fn(),
  ;
);

const useMockRef = mocked(useRef);

describe('66332902', () => 
  afterEach(() => 
    jest.clearAllMocks();
  );
  afterAll(() => 
    jest.resetAllMocks();
  );
  test('should mock ref and offsetWidth', () => 
    const ref =  current:  ;
    Object.defineProperty(ref, 'current', 
      set(_current) 
        if (_current) 
          jest.spyOn(_current, 'offsetWidth', 'get').mockReturnValueOnce(100);
        
        this._current = _current;
      ,
      get() 
        return this._current;
      ,
    );
    useMockRef.mockReturnValueOnce(ref);
    render(<App />);
  );
);

测试结果:(查看日志

 PASS  examples/66332902/index.test.tsx (9.518 s)
  66332902
    ✓ should mock ref and offsetWidth (39 ms)

  console.log
    100

      at examples/66332902/index.tsx:6:13

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        10.106 s

【讨论】:

这不起作用,如果在&lt;App&gt; 内部其他组件调用useRef。知道如何解决这个@slideshowp2 吗?

以上是关于如何使用 jest.mock 模拟 useRef 和反应测试库的主要内容,如果未能解决你的问题,请参考以下文章

jest.mock():如何使用工厂参数模拟 ES6 类默认导入

使用 Jest 模拟的服务导致“不允许 jest.mock() 的模块工厂引用任何超出范围的变量”错误

在jest.mock中Jest'TypeError:不是函数'

每次测试都是Mock模块

Jest.fn-使用jest.mock时返回的值返回未定义

手动模拟不适用于 Jest