16

How can I spy on a class property arrow function using Jest? I have the following example test case which fails with the error Expected mock function to have been called.:

import React, {Component} from "react";
import {shallow} from "enzyme";

class App extends Component {
  onButtonClick = () => {
    // Button click logic.
  };

  render() {
    return <button onClick={this.onButtonClick} />;
  }
}

describe("when button is clicked", () => {
  it("should call onButtonClick", () => {
    const app = shallow(<App />);
    const onButtonClickSpy = jest.spyOn(app.instance(), "onButtonClick");

    const button = app.find("button");
    button.simulate("click");
    expect(onButtonClickSpy).toHaveBeenCalled();
  });
});

I can make the test pass by changing the button's onClick prop to () => this.onButtonClick() but would prefer not to change my component implementation just for the sake of tests.

Is there any way to make this test pass without changing the component implementation?

skyboyer
  • 22,209
  • 7
  • 57
  • 64
DanielGibbs
  • 9,910
  • 11
  • 76
  • 121
  • what if we replace onButtonClick with jest.fn to test whether it is been called and then separately test the onButtonClick function? If this sounds good let me know will add a solution. – Shubham Gupta Sep 22 '18 at 10:59
  • @ShubhamGupta I tried `const onButtonClickSpy = jest.fn(); app.instance().onButtonClick = onButtonClickSpy;` but I still had the same failure. – DanielGibbs Sep 22 '18 at 11:05

1 Answers1

25

According to this enzyme issue and this one, you have two options:


Option 1: Call wrapper.update() after spyOn

In your case, that would be:

describe("when button is clicked", () => {
  it("should call onButtonClick", () => {
    const app = shallow(<App />);
    const onButtonClickSpy = jest.spyOn(app.instance(), "onButtonClick");

    // This should do the trick
    app.update();
    app.instance().forceUpdate();

    const button = app.find("button");
    button.simulate("click");
    expect(onButtonClickSpy).toHaveBeenCalled();
  });
});

Option 2: Don't use class property

So, for you, you would have to change your component to:

class App extends Component {
  constructor(props) {
    super(props);

    this.onButtonClick = this.onButtonClick.bind(this);
 }

  onButtonClick() {
    // Button click logic.
  };

  render() {
    return <button onClick={this.onButtonClick} />;
  }
}
Emile Bergeron
  • 17,074
  • 5
  • 83
  • 129
Léopold Houdin
  • 1,515
  • 13
  • 18
  • 5
    Option 1 doesn't work, and while Option 2 would work, the reason I'm using class properties is to avoid all the manual binding. – DanielGibbs Sep 22 '18 at 11:06