React 测试:React 浅渲染单元测试中的事件处理程序

React Testing: Event handlers in React Shallow Rendering unit tests

背景

我正在尝试学习如何使用 React 浅渲染 TestUtil 并让测试通过,直到我向两者添加了一个 onClick 事件处理程序;似乎我在 Accordion.test.js 中尝试使用的 Accordion.toggle 函数与 Accordian.js 中的 this.toggle 函数肯定有一些区别......但我无法弄清楚.

问题

如何才能通过 Accordian.test.js 中突出显示的两个测试?

重现步骤

  1. 克隆https://github.com/trevordmiller/shallow-rendering-testing-playground
  2. npm install
  3. npm run dev - 当您单击 "Lorem Ipsum"
  4. 时看到该组件正在工作
  5. npm run test:watch - 看到测试失败

要测试像 onClick 这样的用户事件,您必须使用 TestUtils.Simulate.clickSadly:

Right now it is not possible to use ReactTestUtils.Simulate with Shallow rendering and i think the issue to follow should be: https://github.com/facebook/react/issues/1445

有许多问题阻止您的测试通过。

正在看测试"should be inactive by default":

  1. Accordion.toggle 在您的测试中是 属性 的 Accordion class,而 this.toggle 在您的代码中是 属性 Accordion class 的实例 - 因此在这种情况下,您正在比较两个不同的事物。要在测试中访问 'instance' 方法,您可以将 Accordion.toggle 替换为 Accordion.prototype.toggle。如果它不是 this.toggle = this.toggle.bind(this); 在你的构造函数中,它会起作用。这就引出了第二点。

  2. 当您在函数上调用 .bind() 时,它会在运行时创建一个新函数 - 因此您无法将它与原始函数进行比较 Accordion.prototype.toggle。解决此问题的唯一方法是从 render:

    的结果中提取 "bound" 函数
    let toggle = result.props.children[0].props.onClick;
    
    assert.deepEqual(result.props.children, [
      <a onClick={toggle}>This is a summary</a>,
      <p style={{display: 'none'}}>This is some details</p>
    ]);
    

关于你第二次失败的测试"should become active when clicked":

  1. 您尝试调用不存在的 result.props.onClick()。你打算打电话给 result.props.children[0].props.onClick();

  2. React 中存在一个错误,当使用浅层渲染调用 setState 时需要声明一个全局 "document" 变量 - 如何在每种情况下解决这个问题超出了本文的范围问题,但是让测试通过的快速解决方法是在调用 onClick 方法之前添加 global.document = {};。换句话说,您的原始测试有:

    result.props.onClick();
    

    现在应该说:

    global.document = {};
    result.props.children[0].props.onClick();
    

    参见 "Fixing Broken setState()" on this page and this react issue.

  3. 部分

Marcin Grzywaczewski wrote a great article 带有用于测试适用于浅渲染的点击处理程序的解决方法。

给定一个带有 onClick 属性的嵌套元素和一个上下文绑定到组件的处理程序:

render() {
  return (
    <div>
      <a className="link" href="#" onClick={this.handleClick}>
        {this.state.linkText}
      </a>
      <div>extra child to make props.children an array</div>
    </div>
  );
}

handleClick(e) {
  e.preventDefault();
  this.setState({ linkText: 'clicked' });
}

您可以手动调用 onClick 属性的函数值,存入事件对象:

it('updates link text on click', () => {
  let tree, link, linkText;

  const renderer = TestUtils.createRenderer();
  renderer.render(<MyComponent />);

  tree = renderer.getRenderOutput();
  link = tree.props.children[0];
  linkText = link.props.children;

  // initial state set in constructor
  expect(linkText).to.equal('Click Me');

  // manually invoke onClick handler via props
  link.props.onClick({ preventDefault: () => {} });

  tree = renderer.getRenderOutput();
  link = tree.props.children[0];
  linkText = link.props.children;

  expect(linkText).to.equal('Clicked');
});

我已经成功地测试了我在无状态组件中的点击。方法如下:

我的组件:

import './ButtonIcon.scss';

import React from 'react';
import classnames from 'classnames';

const ButtonIcon = props => {
  const {icon, onClick, color, text, showText} = props,
    buttonIconContainerClass = classnames('button-icon-container', {
      active: showText
    });

  return (
    <div
      className={buttonIconContainerClass}
      onClick={onClick}
      style={{borderColor: color}}>
      <div className={`icon-container ${icon}`}></div>
      <div
        className="text-container"
        style={{display: showText ? '' : 'none'}}>{text}</div>
    </div>
  );
}

ButtonIcon.propTypes = {
  icon: React.PropTypes.string.isRequired,
  onClick: React.PropTypes.func.isRequired,
  color: React.PropTypes.string,
  text: React.PropTypes.string,
  showText: React.PropTypes.bool
}

export default ButtonIcon;

我的测试:

it('should call onClick prop when clicked', () => {
  const iconMock = 'test',
    clickSpy = jasmine.createSpy(),
    wrapper = ReactTestUtils.renderIntoDocument(<div><ButtonIcon icon={iconMock} onClick={clickSpy} /></div>);

  const component = findDOMNode(wrapper).children[0];

  ReactTestUtils.Simulate.click(component);

  expect(clickSpy).toHaveBeenCalled();
  expect(component).toBeDefined();
});

重要的是包装组件:

<div><ButtonIcon icon={iconMock} onClick={clickSpy} /></div>

希望对您有所帮助!