ไม่สามารถค้นหาปุ่มเพื่อจำลองการคลิก - 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 ดูที่เก็บนี้

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
ฉันพยายามลบค่าเริ่มต้นออกจากโปรไฟล์คลาส รวมถึงแบบฟอร์ม redux และทำให้เกิดข้อผิดพลาดบางอย่าง นี่คือหนึ่งในการลบการส่งออกออกจากชั้นเรียน - person Christina Mitchell; 13.05.2016
comment
ปล. เพื่อชี้แจงให้กระจ่าง ฉันพยายามลบออกทีละรายการ ไม่ใช่ทั้งสองรายการพร้อมกัน - person Christina Mitchell; 13.05.2016
comment
ลบ default ออกอย่างชัดเจน ไม่ใช่ export ฉันขอแนะนำให้อ่านบันทึกไวยากรณ์ ES6 เกี่ยวกับวิธีการนำเข้า/ส่งออก: 2ality com/2014/09/es6-modules-final.html - person ZekeDroid; 15.05.2016