如何使用 Jest 和 Enzyme 测试 useEffect 中的函数?

How to test a function inside useEffect with Jest and Enzyme?

我正在使用 jest 和 enzyme 测试我的组件。我想在加载整个页面时加载我的组件,所以我在 useEffect 中使用加载事件,这是我的代码

const RatingsAndReviews = (props: RatingsAndReviewsProps) => {

const [pageLoaded, setPageLoaded] = useState<boolean>(false)

const handleLoad = () => {
    if (document.readyState === 'complete') {
      setTimeout(() => {
        setPageLoaded(true)
      }, 1500)
    }
}

React.useEffect(() => {
    window.addEventListener('load', handleLoad)
    return () => {
      window.removeEventListener('load', handleLoad)
    }
}, [])

return (...some code)
}

我想测试这个 handleLoad 函数,但我无法弄清楚如何在我的组件中传递这个模拟函数,因为该组件已经期望 props 并且因为它是 TypeScript 我不能传递所需道具以外的任何其他内容,这是我的测试用例

it('Should run handleLoad function onMount', ()=>{
    jest.spyOn(React, 'useEffect').mockImplementation(f => f())
    const handleLoad = jest.fn();
    wrapper = mount(<RatingsAndReviews {...propObj} />)
    expect(handleLoad).toHaveBeenCalled();
})

我收到这个错误 expect(jest.fn()).toHaveBeenCalled()

Expected number of calls: >= 1
Received number of calls:    0

尽量不要模拟third-party库函数的实现,因为不正确的模拟实现会破坏它的功能。例如,useEffect(effect) react hook 不只是执行 effect 函数。考虑一下,

React defers running useEffect until after the browser has painted.

你怎么嘲笑它?不正确的模拟会导致意想不到的行为。您的测试可能会基于不正确的模拟实现而通过,但代码在实际 运行 时会失败。这就是为什么你最好不要模拟 third-party 库。当然,这不是绝对的。如果 third-party 库的函数很简单并且 self-contained.

你可以模拟它们

对于 React 组件,我们应该做 black-box 测试,只测试组件的行为和功能,而不是实现。我们应该将组件视为一个单元而不是其中的功能。

我们应该测试 pageLoaded 状态改变时呈现的内容。

函数组件内部定义的事件处理程序是私有的,您不能从外部访问它们(测试代码)。所以你不能直接调用它们。相反,您应该通过用户事件触发它们。您的案例的 load 事件。

例如

index.tsx:

import React from 'react';
import { useEffect, useState } from 'react';

export const RatingsAndReviews = (props) => {
  const [pageLoaded, setPageLoaded] = useState<boolean>(false);

  console.log('pageLoaded: ', pageLoaded);
  const handleLoad = () => {
    if (document.readyState === 'complete') {
      setTimeout(() => {
        setPageLoaded(true);
      }, 1500);
    }
  };

  useEffect(() => {
    window.addEventListener('load', handleLoad);
    return () => {
      window.removeEventListener('load', handleLoad);
    };
  }, []);

  return <div>{pageLoaded ? 'loaded' : 'not loaded'}</div>;
};

index.test.tsx:

import { mount } from 'enzyme';
import React from 'react';
import { act } from 'react-dom/test-utils';
import { RatingsAndReviews } from '.';

describe('RatingsAndReviews', () => {
  it('Should run handleLoad function onMount', () => {
    jest.useFakeTimers();
    const wrapper = mount(<RatingsAndReviews />);
    window.dispatchEvent(new Event('load'));
    expect(wrapper.text()).toBe('not loaded');
    act(() => {
      jest.advanceTimersByTime(1500);
    });
    expect(wrapper.text()).toBe('loaded');
  });
});

测试结果:

 PASS  Whosebug/71953030/index.test.tsx (11.883 s)
  RatingsAndReviews
    ✓ Should run handleLoad function onMount (47 ms)

  console.log
    pageLoaded:  false

      at RatingsAndReviews (Whosebug/71953030/index.tsx:7:11)

  console.log
    pageLoaded:  true

      at RatingsAndReviews (Whosebug/71953030/index.tsx:7:11)

-----------|---------|----------|---------|---------|-------------------
File       | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-----------|---------|----------|---------|---------|-------------------
All files  |   93.33 |       75 |      80 |   92.86 |                   
 index.tsx |   93.33 |       75 |      80 |   92.86 | 19                
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        13.575 s, estimated 14 s

包版本:

"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.5",
"react": "^16.14.0",
"react-dom": "^16.14.0",
"jest": "^26.6.3",