如何测试React Hooks中的useEffect和useCallback

3
我可以帮您翻译,以下是翻译结果:

我正在尝试使用Jest、Enzyme编写有关React hooks中的useEffect和useCallback的单元测试用例,但我无法成功。有人能帮我为下面的代码编写一个测试用例吗?

ModalComponent.jsx

  const ModalComponent = ({ closeModal }) => {
     const handleModal = useCallback((event) => {
        if (event.keyCode === 27) {
          closeModal(false);
        }
     }
     useEffect(() => {
        document.addEventListener('keydown', handleModal);
        return () => document.removeEventListener('keydown', handleModal);
     }, []);

     return (
        <Modal>
          <Header onClose={closeModal} />
          <Body />
          <Footer />
        </Modal>
     );
  }

ModalComponent.spec.jsx

  describe('Modal Component', () => {
     let props;
     beforeEach(() => {
       props = {
         closeModal: jest.fn(),
       };
     };

     it('should handle useEffect', () => {
        jest.spyOn(React, 'useEffect').mockImplementation(f => f());
        document.addEventListener('keydown', handleModal); 
        document.removeEventListener('keydown', handleModal);
        const component = shallow(<ModalComponent />);
     });
  });

无法覆盖这些代码行:document.addEventListener('keydown', handleModal);,document.removeEventListener('keydown', handleModal);, if(event.keyCode === 27), closeModal(false)。如何覆盖测试用例?


1
Hooks不是为了测试实现而设计的,而是为了测试行为。 - Estus Flask
@EstusFlask,我成功地通过模拟 useEffect 和 useCallback 来覆盖了这个测试用例。jest.spyOn(React, 'useEffect').mockImplementation(f => f()); - Kumar Pranay
1
这是可能的,但通常不是这样做的。如果没有充分的理由,你不应该嘲笑框架本身,这可能导致纯粹的合成测试无法满足实际预期。这尤其适用于钩子函数。正确的测试方法是触发keydown事件,或者至少监视/模拟document方法并断言它们的调用。 - Estus Flask
@EstusFlask,我该怎么做呢?如果你能提供一个例子,那将非常有帮助。我参考了你在上面评论中提到的链接,他们是通过查找元素并模拟事件来实现的,就像这样wrapper.find('input').simulate('keypress', {key: 'Enter'})。但是在我的情况下,我无法将输入或其他元素传递给find方法,所以你能指导我如何解决这个问题吗? - Kumar Pranay
@EstusFlask,谢谢你的回答。当一个组件卸载时,我该如何使用dispatchEvent?我的意思是,如果我们像这样从React.useEffect()返回函数来卸载它,那么React.useEffect()也会被卸载,对吗?所以,我把我的测试写成了这样。 - Kumar Pranay
显示剩余4条评论
1个回答

3

除非必要,否则不应该模拟React内部,因为这会导致合成测试不符合框架的工作方式,并产生错误的结果。特别是对于像useEffect这样具有隐藏状态的钩子,可能无法按照测试人员的预期工作。

React函数组件不公开组件实例,应通过断言结果进行测试。可以使用间谍断言来加强测试,使结果更少含糊不清。

由于监听器设置在document上,因此需要一个焦点,例如:

jest.spyOn(document, 'addEventListener');
jest.spyOn(document, 'removeEventListener');
const onCloseSpy = jest.fn();
const component = mount(<ModalComponent closeModal={onCloseSpy} />);
expect(component.find(Header).prop('onClose')).toBe(onCloseSpy);

expect(document.addEventListener).toBeCalledTimes(1);
expect(document.addEventListener).toBeCalledWith('keydown', expect.any(Function));

document.dispatchEvent(new KeyboardEvent('keydown', {keyCode: 37}));
expect(onCloseSpy).not.toBeCalled();
document.dispatchEvent(new KeyboardEvent('keydown', {keyCode: 27}));
expect(onCloseSpy).toBeCalledWith(false);


// rerender to make sure listeners are set once
component.setProps({});    
expect(document.addEventListener).toBeCalledTimes(1);
expect(document.removeEventListener).not.toBeCalled();

// unmount
component.unmount();
expect(document.removeEventListener).toBeCalledTimes(1);
const [, callback] = document.addEventListener.mock.calls[0];
expect(document.removeEventListener).toBeCalledWith('keydown', callback);

我在我的机器上尝试了这些测试用例。下面的代码行失败了,然而它无法覆盖 useEffect() 方法。expect(document.addEventListener).toBeCalledTimes(1); - Kumar Pranay
1
我明白了。shallow 存在一个持续的问题,需要像你之前做的那样修补 useEffect,参考链接:https://github.com/enzymejs/enzyme/issues/2086,这是一个很大的缺点。我建议暂时使用 mount 进行深度渲染,如果你不希望嵌套组件干扰测试,可以对其进行存根处理。或者,你也可以尝试使用 https://github.com/mikeborozdin/jest-react-hooks-shallow - Estus Flask

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接