Невозможно найти кнопку для имитации щелчка - Unit Testing React с Mocha, Chai, Enzyme

Я пытаюсь имитировать нажатие кнопки с помощью Enzyme. Я смог написать простые тесты, если элемент отрисовывается, однако забавные тесты, такие как нажатие кнопок и т. Д., Терпят неудачу.

В этом примере ошибка в терминале:

 1) Front End @Profile Component clicks a button :
 Error: This method is only meant to be run on single node. 0 found instead.
  at ShallowWrapper.single (node_modules/enzyme/build/ShallowWrapper.js:1093:17)
  at ShallowWrapper.props (node_modules/enzyme/build/ShallowWrapper.js:532:21)
  at ShallowWrapper.prop (node_modules/enzyme/build/ShallowWrapper.js:732:21)
  at ShallowWrapper.simulate (node_modules/enzyme/build/ShallowWrapper.js:505:28)
  at Context.<anonymous> (cmpnt-profile.spec.js:36:32)

модульный тест:

      describe('Front End @Profile Component', () => {
      const wrapper = shallow(<Profile/>);

     ...(other tests here)...
        it('clicks a button ', () => {

        wrapper.find('button').simulate('click');
        expect(onButtonClick.calledOnce).to.equal(true);
    })
});

компонент:

import _ from 'lodash';
import React, { Component, PropTypes } from 'react';
import { reduxForm } from 'redux-form';
import ExpireAlert from '../components/alert';
import SocialAccount from '../components/socialAccounts'

const FIELDS = {
    name : {
        type : 'input',
        label : 'name'
    },
    username : {
        type : 'input',
        label: 'username'
    },
    email : {
        type : 'input',
        label: 'email'
    }
};

let alert = false;

export default class Profile extends Component {

    componentWillReceiveProps(nextProps){
      if(nextProps.userIsUpdated){
        alert = !alert
      }
    }

    handleSubmit(userData) { // update profile
     userData.id = this.props.userInfo.id
     this.props.updateUserInfo(userData)
    }

    renderField(fieldConfig, field) { // one helper per ea field declared
      const fieldHelper = this.props.fields[field];
      return (
        <label>{fieldConfig.label}
          <fieldConfig.type type="text" placeholder={fieldConfig.label} {...fieldHelper}/>
          {fieldHelper.touched && fieldHelper.error && <div>{fieldHelper.error}</div>}
        </label>
      );
    }

  render() {
    const {resetForm, handleSubmit, submitting, initialValues} = this.props;
      return (
        <div className="login">
          <div className="row">
            <div className="small-12 large-7 large-centered columns">
              <div className="component-wrapper">
                <ExpireAlert 
                    set={this.props.userIsUpdated}
                    reset={this.props.resetAlert}
                    status="success"
                    delay={3000}>
                    <strong> That was a splendid update! </strong>
                </ExpireAlert>
                <h3>Your Profile</h3>
                <SocialAccount
                  userInfo={this.props.userInfo}
                  unlinkSocialAcc={this.props.unlinkSocialAcc}
                />
                <form className="profile-form" onSubmit={this.props.handleSubmit(this.handleSubmit.bind(this))}>
                  <div className="row">
                    <div className="small-12 large-4 columns">
                      <img className="image" src={this.props.userInfo.img_url}/>
                    </div>
                    <div className="small-12 large-8 columns">
                      {_.map(FIELDS, this.renderField.bind(this))}
                    </div>
                    <div className="small-12 columns">
                      <button type="submit" className="primary button expanded" disabled={submitting}>
                        {submitting ? <i/> : <i/>} Update
                      </button>
                    </div>
                  </div>
                </form>
              </div>
            </div>
          </div>
      </div>
   );
  }
}

const validate = values => {
  const errors = {}
     if (!values.name) {
        errors.name = 'Name is Required'
      }
      if (!values.username) {
        errors.username = 'Username is Required'
      } else if (values.username.length > 30) {
        errors.username = 'Must be 30 characters or less'
      }
      if (!values.email) {
        errors.email = 'Email is Required'
      } else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)) {
        errors.email = 'Invalid email address'
      }
  return errors;
}

Profile.propTypes = {
  fields: PropTypes.object.isRequired,
  handleSubmit: PropTypes.func.isRequired,
  resetForm: PropTypes.func.isRequired,
  submitting: PropTypes.bool.isRequired
}

export default reduxForm({
  form: 'Profile',
  fields: _.keys(FIELDS),
  validate
})(Profile)

Любые предложения приветствуются. Я хотел бы сделать несколько потрясающих модульных тестов!


person Christina Mitchell    schedule 13.05.2016    source источник


Ответы (2)


ZekeDroid прав. Другой способ справиться с ситуацией - использовать mount, который является способом Enzyme для глубокой визуализации компонентов, то есть визуализируется все дерево компонентов. Это будет считаться интеграционным тестом, а не модульным тестом. ZekeDroid упоминает: «Это предполагает, что ваш компонент может справиться с отсутствием того, что предоставляет ему reduxForm, что в любом случае является хорошей практикой, так как вам будет легче тестировать», и я считаю, что он ссылается на модульное тестирование. В идеале ваши тесты должны включать как модульные, так и интеграционные тесты. Я создал простой проект, чтобы показать, как выполнять как модульные, так и интеграционные тесты с помощью redux-form. См. это репо.

person Tyler Collier    schedule 26.05.2016

В вашем файле два export default. Это, естественно, будет означать, что второй преобладает над первым. Если вы действительно хотите иметь возможность экспортировать reduxForm версию и сам компонент, как это необходимо для тестирования, удалите слово default из компонента.

В вашем тесте вы shallow визуализируете, то есть дочерние элементы не отображаются. Поскольку вы импортируете reduxForm, ничего особенного не будет отображаться, особенно ваш Profile. Следовательно, если вы удалили ключевое слово default, как я уже упоминал, ваш тест начнет работать, когда вы импортируете его с помощью именованного импорта:

import { Profile } from 'path/to/component';

* Это предполагает, что ваш компонент может справиться с отсутствием того, что reduxForm предоставляет ему, что в любом случае является хорошей практикой, так как вам будет легче протестировать.

person ZekeDroid    schedule 13.05.2016
comment
Я попытался удалить значение по умолчанию из профиля класса, а также форму сокращения, и это привело к некоторым ошибкам. вот один от удаления экспорта из класса. - person Christina Mitchell; 13.05.2016
comment
пс. Чтобы уточнить, я пытался удалять их по одному, а не одновременно. - person Christina Mitchell; 13.05.2016
comment
удалите default, очевидно, не export. Я предлагаю прочитать примечания к синтаксису ES6 о том, как импортировать / экспортировать: 2ality. ru / 2014/09 / es6-modules-final.html - person ZekeDroid; 15.05.2016