spy on wrapper.instance() method not called when using shallow()
See original GitHub issueTesting a component method in isolation can be done via wrapper.instance().method()
, see #208.
Testing a user event actually calls that method can be achieved using jest.spyOn()
and .simulate()
.
To the best of my knowledge, you can spy on either the prototype or instance:
jest.spyOn(Component.prototype, 'method')
jest.spyOn(wrapper.instance(), 'method')
It seems that the prototype spy will work if you render with either shallow()
or mount()
, but when using the instance, mount()
works and shallow()
does not.
This would be fine, just use prototype right! Unfortunately, our team is lazy and don’t enjoy binding every method to this
in the constructor()
, so we use class properties to avoid it. The downside of class properties is that they do not appear on the prototype, so we are forced to spy on the instance. This works perfectly if you render with mount()
but in unit tests, we always use shallow()
so we can test the “unit” in isolation.
Can you see the dilemma? Should shallow()
treat spies differently than mount()
?
To demonstrate the issue below is a component and the associated test:
class App extends Component {
constructor(...args) {
super(...args);
this.handleButtonClick = this.handleButtonClick.bind(this);
}
handleButtonClick(event) {
// prototype method
}
handleAnchorClick = (event) => {
// class property
};
render() {
return (
<div>
<button onClick={this.handleButtonClick}>Click Me!</button>
<a href="#" onClick={this.handleAnchorClick}>Click Me!</a>
</div>
);
}
}
describe('spy using prototype', () => {
it('calls "handleButtonClick()" on button click - using prototype', () => {
const spy = jest.spyOn(App.prototype, 'handleButtonClick');
const wrapper = shallow(<App />);
wrapper.find('button').simulate('click', 'using prototype');
expect(spy).toHaveBeenCalled();
});
// FAILS due to class properties not being on prototype
it('calls "handleAnchorClick()" on anchor click - using prototype', () => {
const spy = jest.spyOn(App.prototype, 'handleAnchorClick');
const wrapper = shallow(<App />);
wrapper.find('a').simulate('click', 'using prototype');
expect(spy).toHaveBeenCalled();
});
});
describe('spy using instance with mount', () => {
it('calls "handleButtonClick()" on button click', () => {
const wrapper = mount(<App />);
const spy = jest.spyOn(wrapper.instance(), 'handleButtonClick');
wrapper.update();
wrapper.find('button').simulate('click');
expect(spy).toHaveBeenCalled();
});
it('calls "handleAnchorClick()" on button click', () => {
const wrapper = mount(<App />);
const spy = jest.spyOn(wrapper.instance(), 'handleAnchorClick');
wrapper.update();
wrapper.find('a').simulate('click');
expect(spy).toHaveBeenCalled();
});
});
// FAILS due to shallow(), not sure why but mount() works as you can see above
describe('spy using instance with shallow', () => {
it('calls "handleButtonClick()" on button click', () => {
const wrapper = shallow(<App />);
const spy = jest.spyOn(wrapper.instance(), 'handleButtonClick');
wrapper.update();
wrapper.find('button').simulate('click');
expect(spy).toHaveBeenCalled();
});
it('calls "handleAnchorClick()" on button click', () => {
const wrapper = shallow(<App />);
const spy = jest.spyOn(wrapper.instance(), 'handleAnchorClick');
wrapper.update();
wrapper.find('a').simulate('click');
expect(spy).toHaveBeenCalled();
});
});
If you want to try it out, take a look at this repo. Any help will be greatly appreciated.
Issue Analytics
- State:
- Created 6 years ago
- Reactions:38
- Comments:48 (28 by maintainers)
@samit4me in your examples if you replace
wrapper.update()
when shallow rendering withwrapper.instance().forceUpdate()
the failing tests will worksee: https://github.com/airbnb/enzyme/issues/622
@ljharb
I’ve been following your advice on constructor binding and it’s performance issues but it seems to conflict with what they’re saying here https://github.com/facebook/react/issues/9851#issuecomment-306221157
tldr; performance wise, doing a constructor bind is identical to having a class property, with the added benefit of also being on the prototype (and the “performance” hit that comes with it)
If you use class properties instead of proper manual this-binding, you’re just stuck.