Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to spy on a class property arrow function using Jest

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?

like image 771
DanielGibbs Avatar asked Sep 22 '18 10:09

DanielGibbs


People also ask

How do you use the Jest spy function?

To spy on an exported function in jest, you need to import all named exports and provide that object to the jest. spyOn function. That would look like this: import * as moduleApi from '@module/api'; // Somewhere in your test case or test suite jest.

How do you mock a class using Jest?

Jest can be used to mock ES6 classes that are imported into files you want to test. ES6 classes are constructor functions with some syntactic sugar. Therefore, any mock for an ES6 class must be a function or an actual ES6 class (which is, again, another function). So you can mock them using mock functions.

How do you mock an object in Jest?

When mocking global object methods in Jest, the optimal way to do so is using the jest. spyOn() method. It takes the object and name of the method you want to mock, and returns a mock function. The resulting mock function can then be chained to a mocked implementation or a mocked return value.


1 Answers

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} />;
  }
}
like image 101
Léopold Houdin Avatar answered Sep 20 '22 13:09

Léopold Houdin