Given the following class component:
// Test.js
import React, { Component } from 'react';
class Test extends Component {
componentDidMount() {
this.func();
}
func = () => {
// noop
};
render() {
return null;
}
}
export default Test;
To verify that componentDidMount
was invoked after component was mounted:
// Test.spec.js
import React from 'react';
import { shallow } from 'enzyme';
import Test from './Test';
it('invokes `componentDidMount` when mounted', () => {
jest.spyOn(Test.prototype, 'componentDidMount');
shallow(<Test />);
expect(Test.prototype.componentDidMount).toHaveBeenCalled();
Test.prototype.componentDidMount.mockRestore();
});
Now what if we want to spy on the class field arrow function?
Could we do something like below?
// Test.spec.js
// ...
it('invokes `func` when mounted', () => {
jest.spyOn(Test.prototype, 'func');
shallow(<Test />);
expect(Test.prototype.func).toHaveBeenCalled();
Test.prototype.func.mockRestore();
});
Unfortunately, we get a failure:
FAIL Test.spec.js
● calls `func` when mounted
Cannot spy the func property because it is not a function; undefined given instead
This is because arrow function class properties aren’t found on the class but on the class instance.
So we have 2 options:
- Spy on the instance method and explicitly invoke the lifecycle method
- Or refactor to bind in constructor instead of arrows for class methods.
Option 1
Spy on the instance method and explicitly call componentDidMount
:
// Test.spec.js
// ...
it('calls `func` when mounted', () => {
const wrapper = shallow(<Test />);
const instance = wrapper.instance();
jest.spyOn(instance, 'func');
instance.componentDidMount();
expect(instance.func).toHaveBeenCalled();
});
Here the component remains unchanged whereas the test case needs to be updated.
Option 2
Refactor from class properties (Stage 3) to bind in constructor (ES2015):
// Test.js
import React, { Component } from 'react';
class Test extends Component {
constructor(props) {
super(props);
this.func = this.func.bind(this);
}
componentDidMount() {
this.func();
}
func() {
// noop
};
render() {
return null;
}
}
export default Test;
Here the test case remains unchanged whereas the component is updated.