使用 Enzyme 测试 Window 滚动事件处理程序的最佳方法是什么?

What is the best way to test Window Scroll Event Handlers with Enzyme?

我一直在与一个新团队一起开发 React 应用程序,讨论围绕为触发 window.scroll 事件方法的组件编写单元测试展开。

所以,让我们以这个组件为例。

import React, { Component } from 'react';

class MyComponent extends Component {
  componentDidMount() {
    window.addEventListener('scroll', this.props.myScrollMethod);
  }

  componentWillUnmount() {
    window.removeEventListener('scroll', this.props.myScrollMethod);
  }

  render() {
    return (
      <div>
        <h1>Hello MyComponent!</h1>
      </div>
    )
  };
};

export default MyComponent;

如您所见,我正在采用一种方法,该方法通过 prop 传递到组件并将其绑定到 window 事件侦听器,其中事件为 scroll。在现实世界中,当用户向下滚动页面时,该组件会调用 myScrollMethod(假设这里的用例是当用户滚动到页面上的某个点之外时显示一个粘性导航栏)。

问题是...我需要找到一种合适的方法来测试它。我的最终目标是创建一个间谍方法,该方法通过 myScrollMethod 道具传递到组件中,然后触发滚动(或在测试中模拟滚动),最后断言滚动处理程序方法是否已触发。这是我的尝试:

import React from 'react';
import sinon from 'sinon';
import expect, { createSpy }  from 'expect';
import { shallow } from 'enzyme';

import MyComponent from './MyComponent';

describe('The <MyComponent /> component', () => {
  let onScroll;
  let MyTestComponent;

  beforeEach(() => {
    onScroll = createSpy();
    MyTestComponent = shallow(
      <MyComponent
        myScrollMethod={onScroll}
        />
    );
  });

  it('Should call the onScroll method when a user scrolls', () => {
    expect(onScroll).toNotHaveBeenCalled();
    window.dispatchEvent(new window.UIEvent('scroll', { detail: 0 }));
    expect(onScroll).toHaveBeenCalled();
  });
});

我遇到的问题是最终断言失败,因为从未调用过间谍程序。我已经参考了该站点上的许多其他帖子,但到目前为止还没有找到合适的解决方案。任何建议都将不胜感激,因为它已经绞尽脑汁一段时间了!

非常感谢!

不幸的是,我认为 Enzyme 在这里不会有太大帮助。该库仅处理 React 合成事件系统中的事件。因此,您使用 Enzyme 呈现的组件将无法与添加到 window 的事件侦听器一起使用。 This issues thread on Enzyme's github 提供了更多详细信息,并且有一些建议的解决方法可能会对您有所帮助。

例如,您可能想监视 window.addEventListener,然后您可以检查 mount 是否使用参数 "scroll" 和您的回调调用它。

关于您的具体代码,滚动侦听器设置在 componentDidMount 中,但您的组件呈现较浅,因此实际上并未调用 componentDidMount(因此没有侦听器)。尝试将此行添加到您的 beforeEachMyTestComponent.instance().componentDidMount()

您可以假设事件只是简单的消息 - 因为酶在幕后使用 JSDOM,您可以充分跟踪这些消息,因为它们使用普通 javascript 附加到节点, 无论事件是 'scroll'、'foo' 还是 'bar'.

在测试环境中,我们并不真正关心事件是否被调用,系统只需要知道如何响应它。

下面是一个跟踪非合成事件的例子,比如用酶滚动:

// scollable.js
class Scrollable extends Component {
  componentDidMount() {
    if (this.myCustomRef) {
      this.myCustomRef.addEventListener('scroll', this.handleScroll)
    }
  }

  handleScroll = (e) => this.props.onScroll(e) 
}

// scollable-test.js
import React from 'react'
import { mount } from 'enzyme'
import Scrollable from '../Scrollable'

describe('shared/Scrollable', () => {
  it('triggers handler when scrolled', () => {
    const onScroll = jest.fn()
    const wrapper = mount(
      <Scrollable onScroll={onScroll}><div /></Scrollable>
    )
    const customEvent = new Event('scroll')
    // the first element is myCustomRef
    wrapper.first().getDOMNode().dispatchEvent(customEvent)
    expect(wrapper.prop('onScroll')).toHaveBeenCalled()
  })
})

在我们将事件附加到 dom 之后,我们可以使用 getDOMNode and dispatchEvent 触发它们的处理程序,这会触发我们的道具 onScroll

这里有一些 JSDOM 的限制,因为如果你需要做一些事情,比如跟踪大小或高度,或者在事件触发后滚动节点的顶部,你就不走运了 -这是因为 JSDOM 实际上并没有为您呈现页面,而是 'emulates' DOM 用于与像酶这样的库一起使用 - 也可以说满足这些需求的测试会更适合端到端测试、无头浏览器或完全不同的工具。