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

IT技术 reactjs mocha.js enzyme
2021-05-23 16:04:21

我一直在与一个新团队一起开发 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 传递到组件中,并将其绑定到窗口事件侦听器,事件是scroll. 在现实世界中,这个组件会myScrollMethod在用户向下滚动页面时调用(假设这里的用例是当用户滚动超过页面上的某个点时显示粘性导航栏)。

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

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();
  });
});

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

非常感谢!

2个回答

不幸的是,我认为酶在这里不会有太大帮助。该库仅处理 React 的合成事件系统内的事件。因此,您使用 Enzyme 渲染的组件不适用于添加到窗口的事件侦听器。Enzyme 的 github 上的这个问题线程提供了更多详细信息,并且有一些建议的解决方法可能会对您有所帮助。

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

关于您的特定代码,滚动侦听器已设置,componentDidMount但您的组件是浅渲染的,因此componentDidMount实际上并未被调用(因此没有侦听器)。尝试将此行添加到您的beforeEachMyTestComponent.instance().componentDidMount()

你可以证明事件只是简单的消息——因为酶在底层使用 JSDOM,你可以充分跟踪这些消息,因为它们使用普通的 javascript 附加到节点,无论事件是“滚动”还是“foo” ,或“酒吧”。

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

这是跟踪非合成事件(如使用酶滚动)的示例:

// 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 之后,我们可以通过使用getDOMNodedispatchEvent来触发它们的处理程序,这会触发我们的propsonScroll

JSDOM 有一些限制,因为如果你需要做一些事情,比如跟踪大小或高度,或者事件触发后节点的 scrollTop,那你就不走运了——这是因为 JSDOM 实际上并不渲染页面,而是“模拟”DOM 以与酶等库一起使用 - 也可以说,具有这些需求的测试更适合端到端测试、无头浏览器或完全不同的工具。