Тестирование на React: обработчики событий в тестах React Shallow Rendering

Фон

React Shallow Rendering tests

Я пытаюсь узнать, как использовать React Shallow Rendering TestUtil, и прошел тесты, пока я не добавил обработчик событий onClick для обоих; Кажется, что существует некоторая разница с функцией Accordion.toggle, которую я пытаюсь использовать в Accordion.test.js vs this.toggle в Accordian.js... но я не могу понять это.

Вопрос

Как я могу передать два выделенных теста в Accordian.test.js?

Шаги по воспроизведению

Ответ 1

Существует ряд проблем, препятствующих прохождению тестов.

Глядя на тест, "по умолчанию неактивен":

  • Accordion.toggle в вашем тесте является свойством класса Accordion, а this.toggle в вашем коде является свойством экземпляра класса Accordion, поэтому в этом случае вы сравниваете два разные вещи. Чтобы получить доступ к методу "instance" в вашем тесте, вы можете заменить Accordion.toggle на Accordion.prototype.toggle. Это будет работать, если бы не для this.toggle = this.toggle.bind(this); в вашем конструкторе. Это приводит нас ко второму вопросу.

  • Когда вы вызываете функцию .bind() для функции, она создает новую функцию во время выполнения, поэтому вы не можете сравнить ее с исходным Accordion.prototype.toggle. Единственный способ обойти это - вытащить "связанную" функцию из результата рендеринга:

    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>
    ]);
    

Что касается вашего второго теста на отказ, "должен активироваться при нажатии":

  • Вы пытаетесь вызвать result.props.onClick(), который не существует. Вы хотели называть result.props.children[0].props.onClick();

  • В React есть ошибка, которая требует, чтобы глобальная переменная "document" была объявлена ​​при вызове setState с мелким рендерингом - как обойти это при любых обстоятельствах выходит за рамки этого вопроса, но быстро работает чтобы получить ваши тесты, нужно добавить global.document = {}; прямо перед вызовом метода onClick. Другими словами, у вашего оригинального теста было:

    result.props.onClick();
    

    Теперь скажите:

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

    См. раздел "Исправление поврежденного setState()" на этой странице и эта проблема реагирования.

Ответ 2

Марцин Гживачевски написал статью с обходным решением для тестирования обработчика кликов, который работает с мелким рендерингом.

Для вложенного элемента с onClick prop и обработчиком с контекстом, связанным с компонентом:

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 prop, stubbing в объекте события:

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

Ответ 3

Для тестирования пользовательских событий, таких как onClick, вам нужно будет использовать TestUtils.Simulate.click. Печально:

В настоящий момент невозможно использовать ReactTestUtils.Simulate с мелким рендерингом, и я думаю, что следующая проблема должна быть следующей: https://github.com/facebook/react/issues/1445

Ответ 4

Я успешно проверил свой клик в моем компоненте без гражданства. Вот как:

Мой компонент:

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>

Надеюсь, что это поможет!