I'm trying to write a Jest test for a simple React component to confirm that a function has been called when I simulate a click.
However, when I use spyOn method, I keep getting TypeError: Cannot read property 'validateOnSave' of undefined. My code looks like this:
OptionsView.js
class OptionsView extends React.Component {
constructor(props) {
super(props);
this.state = {
reasonCode: null,
remarkCode: null,
otherCode: null,
codeSelectionIsInvalid: [false, false, false],
};
this.validateOnSave = this.validateOnSave.bind(this);
this.saveOptions = this.saveOptions.bind(this);
validateOnSave() {
const copy = this.state.codeSelectionIsInvalid;
copy[0] = !this.state.reasonCode;
copy[1] = !this.state.remarkCode;
copy[2] = !this.state.otherCode;
this.setState({ codeSelectionIsInvalid: copy });
if (!copy[0] && !copy[1] && !copy[2]) {
this.saveOptions();
}
}
saveOptions() {
const { saveCallback } = this.props;
if (saveCallback !== undefined) {
saveCallback({ reasonCode: this.state.reasonCode, remarkCode: this.state.remarkCode, otherCode: this.state.otherCode,
});
}
}
render() {
const cx = classNames.bind(styles);
const reasonCodes = this.props.reasonCodeset.map(reasonCode => (
<Select.Option
value={reasonCode.objectIdentifier}
key={reasonCode.objectIdentifier}
display={`${reasonCode.name}`}
/>
));
const remarkCodes = this.props.remarkCodeset.map(remarkCode => (
<Select.Option
value={remarkCode.objectIdentifier}
key={remarkCode.objectIdentifier}
display={`${remarkCode.name}`}
/>
));
const otherCodes = this.props.otherCodeset.map(otherCode => (
<Select.Option
value={otherCode.objectIdentifier}
key={otherCode.objectIdentifier}
display={`${otherCode.name}`}
/>
));
return (
<ContentContainer fill>
<Spacer marginTop="none" marginBottom="large+1" marginLeft="none" marginRight="none" paddingTop="large+2" paddingBottom="none" paddingLeft="large+2" paddingRight="large+2">
<Fieldset legend="Code sets">
<Grid>
<Grid.Row>
<Grid.Column tiny={3}>
<SelectField selectId="reasons" required placeholder="Select" label="Reasons:" error="Required field is missing" value={this.state.reasonCode} onChange={this.updateReasonCode} isInvalid={this.state.codeSelectionIsInvalid[0]}>
{reasonCodes}
</SelectField>
</Grid.Column>
</Grid.Row>
<Grid.Row>
<Grid.Column tiny={3}>
<SelectField selectId="remarks" required placeholder="Select" label="Remarks:" error="Required field is missing" value={this.state.remarkCode} onChange={this.updateRemarkCode} isInvalid={this.state.codeSelectionIsInvalid[1]}>
{remarkCodes}
</SelectField>
</Grid.Column>
</Grid.Row>
<Grid.Row>
<Grid.Column tiny={3}>
<SelectField selectId="other-codes" required placeholder="Select" label="Other Codes:" error="Required field is missing" value={this.state.otherCode} onChange={this.updateOtherCode} isInvalid={this.state.codeSelectionIsInvalid[2]}>
{otherCodes}
</SelectField>
</Grid.Column>
</Grid.Row>
</Grid>
</Fieldset>
</Spacer>
<ActionFooter
className={cx(['action-header-footer-color'])}
end={(
<React.Fragment>
<Spacer isInlineBlock marginRight="medium">
<Button text="Save" onClick={this.validateOnSave} />
</Spacer>
</React.Fragment>
)}
/>
</ContentContainer>
);
}
}
OptionsView.propTypes = propTypes;
export default injectIntl(OptionsView);
OptionsView.test
describe('RemittanceOptions View', () => {
let defaultProps = {...defined...}
beforeAll(() => {
Object.defineProperty(window, "matchMedia", {
value: jest.fn(() => {
return {
matches: true,
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
}
})
});
});
it('should validate remit codes on save', () => {
const wrapper = mountWithIntl(<OptionsView
{...defaultProps}
/>);
const instance = wrapper.instance();
const spy = jest.spyOn(instance, "validateOnSave");
wrapper.setState({
reasonCode: 84,
remarkCode: 10,
otherCode: null
});
console.log(wrapper.find('Button[text="Save"]').debug());
const button = wrapper.find('Button[text="Save"]').at(0);
expect(button.length).toBe(1);
button.simulate('click');
expect(spy).toHaveBeenCalled();
expect(wrapper.state('codeSelectionIsInvalid')).toEqual([false,false,true]);
});
});
Ultimate goal is to test two cases when save is clicked:
When state.codeSelectionIsInvalid: [false,false,true]
When state.codeSelectionIsInvalid: [false,false,false]
Where am I going wrong here. Any help is appreciated!
If you get an error, “ Cannot spy the fetch property because it is not a function; undefined given instead ”, that’s because fetch has not been polyfill’d in your Jest’s JSDOM environment. As of this writing, there is an open request ( jsdom/jsdom#1724) to add fetch API headers into JSDOM.
Note that in Jest, spies are mocks and can also be stubs since they are registering the calls (mock) and can override the returned value (stub). With all the jargon out of the way, let’s write some tests.
Add to that the fact that the term “mock” is ambiguous; it can refer to functions, modules, servers etc. I would like to help you get familiar not only with mocking features in Jest, but these testing concepts in general.
Now what if we want to spy on the class field arrow function? Could we do something like below? This is because arrow function class properties aren’t found on the class but on the class instance. Spy on the instance method and explicitly invoke the lifecycle method Or refactor to bind in constructor instead of arrows for class methods.
After hours of debugging, found out that the instance didn't have any methods bound. Since it is a connected component, using shallowWithIntl()
and dive()
resolved the error.
it('should validate remit codes on save', () => {
const wrapper = shallowWithIntl(<RemitOptionsView
{...testProps}
/>);
const button = wrapper.dive().find('Button[text="Save"]'); //Not finding the button
const instance = wrapper.dive().instance();
const spy = jest.spyOn(instance, 'validateOnSave');
instance.validateOnSave();
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With