Jest: Cannot spy the property because it is not a function; undefined given instead

45,929

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();
  });

Action Footer

Share:
45,929
babybear
Author by

babybear

Updated on December 12, 2020

Comments

  • babybear
    babybear over 3 years

    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:

    1. When state.codeSelectionIsInvalid: [false,false,true]

    2. When state.codeSelectionIsInvalid: [false,false,false]

    Where am I going wrong here. Any help is appreciated!