13

Say I have the following application:

class Child extends React.Component {
    render() {
        return <button onClick={this.handleChildOnClick}>{this.props.children}</button>;
    }

    handleChildOnClick() {
        this.props.onChildClick('foo');
    }
}

class Parent extends React.Component {
    render() {
        return <Child onChildClick={this.handleParentOnClick}>click me</Child>;
    }

    handleParentOnClick(text) {
        this.props.onParentClick(12345);
    }
}

class App extends React.Component {
    render() {
        return <Parent onParentClick={(num) => console.log(num)} />;
    }
}

I'm having a hard time figuring out the proper way to test the Parent component. The Child and App one are not a problem, but the Parent...

I mean, how do I test that a click on the Child component is going to invoke the Parent click callback without:

  1. ...rendering Child. Parent should be tested in isolation as a shallow render. Child will also be tested in isolation and if I do a mount render, I'm basically testing the click callback on the Child twice.
  2. ...directly invoking handleParentOnClick on the Parent instance. I shouldn't depend on the exact implementation of Parent for this. If I change the name of the callback function, the test will break and it could very well be a false positive.

Is there a third option?

2 Answers 2

17

While testing Parent, you can do:

import { shallow } from 'enzyme';
import { stub } from 'sinon';

describe('<Parent/>', () => {
  it('should handle a child click', () => {
    const onParentClick = stub();
    const wrapper = shallow(<Parent onParentClick={onParentClick} />);
    wrapper.find("Child").prop('onChildClick')('foo');
    expect(onParentClick.callCount).to.be.equal(1);
    // You can also check if the 'foo' argument was passed to onParentClick
  });
});
Sign up to request clarification or add additional context in comments.

3 Comments

Enzyme's find method accepts a React class as a selector, so you probably want to remove the quotes around Child (line 8). Aside from that, this is nearly verbatim that the answer I would give
This was the answer I was looking for, thank you very much =) @JordanBonitatis That will require an additional import while the quote example won't. Besides a string being more error prone, is there any other advantage in passing a class vs a string as a selector?
This doesn't work for me in the case when the child class is a MaterialUI component. Any ideas off the top of your head why?
0

I think this could give you some idea.

// Component

class Child extends React.Component {
  render() {
    return <button onClick={this.handleChildOnClick} className="t-btn">{this.props.children}</button>;
  }
  handleChildOnClick() {
    this.props.onChildClick('foo');
  }
}

// Test

import { spy, stub } from 'sinon';
import { shallow } from 'enzyme';

describe('Child Component', () => {
  it('should check handle click', () => {
    spy(Child.prototype, 'handleChildOnClick');
    const onChildClick = stub();
    const wrapper = shallow(<Child onChildClick={onChildClick}>);
    wrapper.find(".t-btn").simulate('click');
    expect(Child.prototype.handleChildOnClick.callCount).to.equal(1);
  });

  it('should check onChildClick', () => {
    const onChildClick = stub();
    const wrapper = shallow(<Child onChildClick={onChildClick}>);
    wrapper.find(".t-btn").simulate('click');
    expect(onChildClick.callCount).to.equal(1);
  });
});

To test parent with child component

import { stub } from 'sinon';
import { shallow } from 'enzyme';
import Child from '../Components/Child';

describe('Parent Component', () => {
  it('should check handle click', () => {
    const onParentClick = stub();
    const wrapper = shallow(<Parent onParentClick={onParentClick} />);
    wrapper.find(".t-btn").simulate('click');
    expect(Child.prototype.handleChildOnClick.callCount).to.equal(1);
  });

  it('should check onChildClick', () => {
    const onChildClick = stub();
    const wrapper = shallow(<Child onChildClick={onChildClick}>);
    wrapper.find(Child).prop('onChildClick')('foo');
    expect(onParentClick.callCount).to.be.equal(1);
  });
});

Above code just handle only one component, but I hope this could give you some gist. Sorry if the syntax is breaking anywhere..

1 Comment

This was not the answer I was looking for... You're just testing the Child component, not the Parent one (which the question was about). Thank for you contribution though.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.