无法找到按钮来模拟点击 - 单位测试React w / Mocha,Chai,Enzyme

时间:2016-05-13 17:22:50

标签: unit-testing reactjs 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)

任何建议表示赞赏。我很乐意做一些惊人的单元测试!

2 个答案:

答案 0 :(得分:2)

ZekeDroid是正确的。处理这种情况的另一种方法是使用mount,这是Enzyme深度渲染组件的方式,意味着渲染整个组件树。这将被视为集成测试,而不是单元测试。 ZekeDroid提到“这假设您的组件可以处理不接收reduxForm提供的任何内容,这无论如何都是好的做法,因为您会发现它更容易测试。”,我相信他正在引用单元测试。理想情况下,您的测试应包括单元测试和集成测试。我创建了一个简单的项目来展示如何使用redux-form进行单元测试和集成测试。请参阅this repo

答案 1 :(得分:0)

您的文件中有两个export default个。这自然意味着第二个覆盖了第一个。如果您真的希望能够导出reduxForm版本和组件本身,就像您应该进行测试一样,那么请从组件中删除单词default

在您的测试中,您shallow渲染,意味着不渲染任何子项。由于您导入了reduxForm,因此无法呈现任何内容,绝对不是您的Profile。因此,如果您删除了我提到的default关键字,那么当您使用命名导入导入测试时,测试将开始工作:

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

*这假设您的组件可以处理不接收任何reduxForm提供的内容,这无论如何都是很好的做法,因为您会发现它更容易测试。