Testing React Event Handlers

Can someone please tell me how to test a React component's event handler that looks like this ...

handleChange(e) {
    this.setObjectState(e.target.getAttribute("for").toCamelCase(), e.target.value);
}

setObjectState(propertyName, value) {
    let obj = this.state.registrationData;
    obj[propertyName] = value;
    this.setState({registrationData: obj});
}

      

I wrote a test using Enzyme to test the rendering, and this will allow me to mock events to verify that the handlers are actually called, but that is the easy part. I want to check what happens when the event code runs. I can run them manually, but I don't know what to pass in the "e" parameter in the test. If I use an enzyme, the test crashes until I close the event handler, since "e" is undefined.

Here's the enzyme test bits ...

    describe("uses internal state to", () => {
        let stateStub = null;
        let formWrapper = null;

        beforeEach(() => {
            wrapper = shallow(<RegistrationForm />);
            instance = wrapper.instance();
            stateStub = sinon.stub(instance, 'setState');
            formWrapper = wrapper.find(Form.Wrapper);
        });

        afterEach(() => {
            stateStub.restore();
        });

        it("handle changing an email address", () => {
            formWrapper.find("[name='Email']").simulate('change')
            sinon.called(stateStub);
        })
    });

      

I briefly covered using "mount" instead of "shallow", but I couldn't get it to work at all. This has a lot of many problems like not being able to stub things like loading data for a search dropdown before it tries to execute.

Here's what I'm trying to (ideally) do ...

    describe("ALT uses internal state to", () => {
        let stateStub = null;
        let formWrapper = null;

        beforeEach(() => {
            wrapper = shallow(<RegistrationForm />);
            instance = wrapper.instance();
            stateStub = sinon.stub(instance, 'setState');
        });

        afterEach(() => {
            stateStub.restore();
        });

        it("handle changing an email address", () => {
            let e = 'some fake data - what IS this object?';
            instance.handleChange(e);
            sinon.called(stateStub);
            sinon.calledWith({registrationData: errr.. what?});
        })
    });

      

As requested, here's the complete component code ...

import ErrorProcessor from "./error-processor";
import Form from "../../../../SharedJs/components/form/index.jsx"
import HiddenState from "../../data/hidden-state"
import LookupRestServiceGateway from "../../../../SharedJs/data/lookup-rest-service-gateway"
import React from "react";
import RegistrationRestServiceGateway from "../../data/registration-rest-service-gateway"

export default class RegistrationForm extends React.Component {

    constructor(props) {
        super(props);
        this.state = props.defaultState || { 
            registrationData: {
                email: "",
                password: "",
                confirmPassword: "",
                firstName: "",
                lastName: "",
                employerID: null
            },
            registered: false,
            employersLookupData: []
        };

        this.formId = "registration-form";
        this.errorProcessor = new ErrorProcessor();
        this.employersDataSource = new LookupRestServiceGateway(`/api/lookups/employers/${HiddenState.getServiceOperatorCode()}`);
        this.registrationGateway = new RegistrationRestServiceGateway();

        this.handleChange = this.handleChange.bind(this);
        this.handleEmployerChange = this.handleEmployerChange.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
    }

    handleChange(e) {
        this.setObjectState(e.target.getAttribute("for").toCamelCase(), e.target.value);
    }

    handleEmployerChange(e) {
        this.setObjectState("EmployerID", e.target.value);
    }

    handleSubmit() {
        this.submitRegistration();
    }

    componentDidMount() {
        this.loadLookupData();
    }

    loadLookupData() {
        this.employersDataSource.getListItems({ successCallback: (data) => {
            this.setState({ employersLookupData: data ? data.items : [] });
        }});
    }

    setObjectState(propertyName, value) {
        let obj = this.state.registrationData;
        obj[propertyName] = value;
        this.setState({registrationData: obj});
    }

    submitRegistration() {
        this.registrationGateway.register({
            data: this.state.registrationData,
            successCallback: (data, status, xhr) => {
                this.setState({registered: true});
                if (data.errors && data.errors.length) {
                    this.errorProcessor.processErrorObject(this.formId, xhr);
                }
            },
            errorCallback: (xhr) => {
                this.errorProcessor.processErrorObject(this.formId, xhr);
            }
        });
    }

    render() {
        return (this.state.registered ? this.renderConfirmation() : this.renderForm());
    }

    renderConfirmation() {
        return (
            <div className = "registration-form">
                <p>Your registration has been submitted. An email will be sent to you to confirm your registration details before you can log in.</p>
                <Form.ErrorDisplay />
            </div>
        );
    }

    renderForm() {
        return (
            <Form.Wrapper formId = {this.formId}
                          className = "registration-form form-horizontal"
                          onSubmit = {this.handleSubmit}>
                <h4>Create a new account.</h4>
                <hr/>
                <Form.ErrorDisplay />
                <Form.Line name = "Email" 
                           label = "Email" 
                           type = "email"
                           inputClassName = "col-md-10 col-sm-9" labelClassName = "col-md-2 col-sm-3"
                           value = {this.state.registrationData.email}
                           onChange = {this.handleChange} />
                <Form.Line name = "Password" 
                           label = "Password" 
                           type = "password"
                           inputClassName = "col-md-10 col-sm-9" labelClassName = "col-md-2 col-sm-3"
                           value = {this.state.registrationData.password}
                           onChange = {this.handleChange} />
                <Form.Line name = "ConfirmPassword" 
                           label = "Confirm Password" 
                           type = "password"
                           inputClassName = "col-md-10 col-sm-9" labelClassName = "col-md-2 col-sm-3"
                           value = {this.state.registrationData.confirmPassword}
                           onChange = {this.handleChange} />
                <Form.Line name = "FirstName" 
                           label = "First Name" 
                           inputClassName = "col-md-10 col-sm-9" labelClassName = "col-md-2 col-sm-3"
                           value = {this.state.registrationData.firstName}
                           onChange = {this.handleChange} />
                <Form.Line name = "LastName" 
                           label = "Last Name" 
                           inputClassName = "col-md-10 col-sm-9" labelClassName = "col-md-2 col-sm-3"
                           value = {this.state.registrationData.lastName}
                           onChange = {this.handleChange} />
                <Form.DropDownLine name = "EmployerID"
                                   label = "Employer"
                                   inputClassName = "col-md-10 col-sm-9" labelClassName = "col-md-2 col-sm-3"
                                   emptySelection = "Please select an employer&hellip;"
                                   onChange = {this.handleEmployerChange}
                                   selectedValue = {this.state.registrationData.employerID}
                                   items = {this.state.employersLookupData}/>                                    
                <Form.Buttons.Wrapper className="col-sm-offset-3 col-md-offset-2 col-md-10 col-sm-9">
                    <Form.Buttons.Submit text = "Register"
                                         icon = "fa-user-plus" />
                </Form.Buttons.Wrapper>     
            </Form.Wrapper>                                                                                                                  
        );
    }
}

RegistrationForm.PropTypes = {
    defaultState: React.PropTypes.object
}

      

I managed to get this to work as it does now, but this is very garbage. Enzyme mount seems to be the way to go, but it introduces so many problems of its own and my spec file is already 10 times the size of my component and it all seems pretty pointless at this level ...

    describe("uses internal state to", () => {
        let stateStub = null;
        let formWrapper = null;

        beforeEach(() => {
            wrapper = shallow(<RegistrationForm />);
            instance = wrapper.instance();
            formWrapper = wrapper.find(Form.Wrapper);
            stateStub = sinon.stub(instance, 'setState');
        });

        afterEach(() => {
            stateStub.restore();
        });

        it("handle changing an email address", () => {
            $("body").append(`<input type="email" for="Email" id="field" class="form-control form-control " maxlength="10000" value="">`);
            let node = $("#field")[0];
            node.value = "new@ddress.co.uk";
            instance.handleChange({target: node});
            sinon.assert.called(stateStub);
        })
    });

      

+3


source to share


1 answer


You can simulate an action to call a method and for example check the state of a component after simulation.

class Foo extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  render() {
    const { count } = this.state;
    return (
      <div>
        <div className={`clicks-${count}`}>
          {count} clicks
        </div>
        <a onClick={() => this.setState({ count: count + 1 })}>
          Increment
        </a>
      </div>
    );
  }
}

const wrapper = shallow(<Foo />);

expect(wrapper.find('.clicks-0').length).to.equal(1);
wrapper.find('a').simulate('click');
expect(wrapper.find('.clicks-1').length).to.equal(1);

      



If you are using Jest try following this example in the official documentation .

Hope it helps.

+1


source







All Articles