enzyme: spy on component method failed

I’m now using class properties feature on component method, but the spy seems not work.

The component like this:

// Sample.js
export default class Sample extends Component {
  sampleMethod = () => {
    console.log("sample");
  }
  render() {
    return (<button onClick={this.sampleMethod}>Sample</button>);
  }
}

Test code here:

// Sample.test.js
import { mount } from "enzyme";
import expect from "expect";
import Sample from "./Sample.js";

const wrapper = mount(<Sample />);
const spy = expect.spyOn(wrapper.instance(), "sampleMethod");
expect(spy).toNotHaveBeenCalled();
wrapper.find("#sample").simulate("click");
expect(spy).toHaveBeenCalled();

The result is spy was not called, is that class properties issue or just I wrote wrongly?

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 25
  • Comments: 51 (22 by maintainers)

Most upvoted comments

@tpai right, because you’re using class properties. I’m suggesting your component instead be:

// Sample.js
export default class Sample extends Component {
  constructor(...args) {
    super(...args);
    this.sampleMethod = this.sampleMethod.bind(this);
  }

  sampleMethod() {
    console.log("sample");
  }

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

and then in your test:

// Sample.test.js
import { mount } from "enzyme";
import expect from "expect";
import Sample from "./Sample.js";

const spy = expect.spyOn(Sample.prototype, "sampleMethod");
const wrapper = mount(<Sample />);
expect(spy).toNotHaveBeenCalled();
wrapper.find("#sample").simulate("click");
expect(spy).toHaveBeenCalled();

@siemiatj First of all, class fields isn’t in ES7; it’s still stage 3, so it’s ES nothing. Second, ES5 remains the only correct way to do this, I’m afraid. Use things because they’re correct, not because they’re shiny and new 😃

I using component method as class properties, however, calling wrapper.update() after spyOn doesn’t work for me. Calling another wrapper.instance().forceUpdate() solved my problem.

I’m using jest 22.1.3.

export default class Button extends React.Component {
    // ...
    handleTouchTap = () => {
        // ...
    }
    // ...
    render() {
        return <button onTouchTap={this.handleTouchTap}>{this.props.children}</button>
    }
}
const callback = jest.fn()
const wrapper = shallow(
    <Button onTouchTap={callback}>Sample</Button>
)
const handleTouchTap = jest.spyOn(wrapper.instance(), 'handleTouchTap')
wrapper.instance().forceUpdate()
wrapper.update()
wrapper.find('button').first().simulate('touchTap')
expect(callback.mock.calls.length).toBe(1)
expect(handleTouchTap.mock.calls.length).toBe(1)

The problem is that the component is rendered before you spy on it, and so the onClick is already bound to the original.

It is a class properties issue in that, if you made it a prototype method, you could spy on Sample.prototype.sampleMethod before calling mount, and it would work. As it is, I think you’d probably have to .update() the wrapper after spying, and before your assertions and simulate call.

wrapper.update() after the spyOn is the other alternative.

@GarrettGeorge no; refactor them to be correct (ie, not arrow functions in class properties) and everything will be fine.

Spying on instance methods won’t work properly if it’s using arrow functions

This is a frustrating step backwards, because it used to work with React 15.6 & enzyme 2.9.1. I had put a personal project aside for a while, and this weekend decided to update it to newer dependencies…only to find that when it comes to testing what used to work now not only doesn’t work but has no upgrade path possible except to change the code to fit the testing tools (yuck!).

Adding *.bind(this) in the constructors of every class simply adds more boilerplate and runs the risk for forgetting to bind newly added functions all the time. I hate having to remember to do that - and it’s not typically an oversight that editors help you to find.

I can understand that the big changes with React 16 would have altered how enzyme does things, but I really do think that being incapable of mocking/spying on instance methods is an issue that needs to be addressed.

Spying on instance methods won’t work properly if it’s using arrow functions in class properties - avoid them; use constructor-bound instance methods instead.

Peeps, its easy as this,

let cmp = shallow(<Slider />);
const renderSlides = jest.spyOn(cmp.instance(), 'renderSlides');
cmp.instance().forceUpdate();
expect(renderSlides.mock.calls.length).toBe(1);

I refuse to write bind this like an absolute heathen everytime. Keep pushing forward people!

Ah, re-reading the thread, I think I see what you’re saying. The semantics of class field arrow functions (not existing until the instance is created) means you can’t pre-emptively spy on them. My bad!

@AndyBarron it’s not a question of them being “supported”; the way they work in the language means that never, until the end of time, should you use an arrow function in a class field.

In other words, this isn’t enzyme refusing to support a popular paradigm, this is a popular paradigm being incorrect in the context of javascript itself, and enzyme being powerless to overcome this.

Will arrow function class properties be supported when they make it into the spec? Or will we still insist on not supporting a popular paradigm in Enzyme?

I wouldn’t expect it to have worked in React 15 and/or in enzyme 2; this isn’t a new issue, it’s an inherent property of using arrow functions in class properties, and it’s why they never have, and never will, be a good idea.

Can you show me the code that passed in enzyme 2 and react 15?

I think its safe to say that many people prefer these ES nothing class fields, and inline declarations for simple functions, because they eliminate cognitive overhead. If it’s something as trivial as onClick={() => this.setState({ modalOpen: true })}, creating a prototype method on the class and binding it on the constructor seems like overkill, and though it is more performant, it’s a micro-optimization that yields no visible impact to the dev/user in my experience.

That being said, I understand Jordan’s argument and I feel like there needs to be a push from the react community to better teach this distinction, perhaps a discussion among leading devs summarising the pros/cons and recommendations would be a good step towards educating the developer community.

Why do you guys have to rain on my arrow function parade? I am going to start using bind.this now, because if you cant beat them, join them!

@DZuz14 this also won’t work if you’re testing things that only do something when mounting

But we’re given the arrow functions to avoid binding manually. So this is going back from ES7 to ES5 😦

Remember to spy.restore() after your test has completed, otherwise the spy will persist across tests – you also won’t be able to spy again on the same prototype method unless it was restored.

@ljharb IT WORKED! Thanks dude, you save my day. 😃

I wish some prominent members of the React team could publicly address this issue. From what I see, the use of fat arrows in class properties and the use of inline arrow functions is becoming pretty widespread. I know a few companies that say they use them, but since I do not work for them, I am not really sure how they are testing their components, or if they are even testing them at all(you would be surprised the companies that don’t do testing at all). Even people in the React community that could be seen as “trustworthy” provide code examples with fat arrows being used in this manner, which is obviously misleading people.

I am currently writing a style guide for my team, and am still wondering where arrow functions can be used, or if they should be used at all. For now, I guess I am going to say don’t use arrow functions in the following ways:

  • As class properties (Methods)
  • Using in event handlers. So no: onClick={() => this.handleClick()}

I would really like to have a uniform way of testing that everyone can agree on. Using forceUpdate or instance() seems like a hack, which could potentially lead to problems down the road.

What is also confusing is that even the React docs show examples of arrow functions being used https://reactjs.org/docs/handling-events.html#passing-arguments-to-event-handlers

@ljharb This thread has me second guessing if they should be used at all. Do you guys use them anywhere in your React code at Airbnb?

Spying on instance methods won’t work properly if it’s using arrow functions in class properties - avoid them; use constructor-bound instance methods instead.

@ljharb Now arrow function is standard, enzyme can support it?

@brettcrowell yes, that’s exactly (part of) why you want to constructor-bind instead of using arrows in class functions.

You have to do the spying before creating the wrapper. Specifically, I’d suggest creating the wrapper Isidre each it rather than using a beforeEach (DRY is often counterproductive in tests)

So is there no way to mock/spy on these arrow functions in class properties just to have a toHaveBeenCalled test pass?

Well yeah I know. I think we’re just getting lazy because of this new syntax sugar.

Thank you for this @ljharb 😃 I was having a similar issue and noticed that the spy would be called only when I simulated a click twice. This was because I was spying on the method after mounting the component, so the spy was not attached to the mounted component. Once I had clicked once, that would be enough to ‘update’ the component, so then when I click again the spy was called. I fixed this by doing what you wrote. Now it works with one click as expected