title: Jest: Cannot spy the property because it is not a function; undefined given instead
body: <p>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. </p>
  
  <p><strong>However, when I use spyOn method, I keep getting TypeError: Cannot read property 'validateOnSave' of undefined.</strong> My code looks like this:</p>
  
  <p><strong>OptionsView.js</strong></p>
  
  <pre><code>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] &amp;&amp; !copy[1] &amp;&amp; !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 =&gt; (
        &lt;Select.Option
          value={reasonCode.objectIdentifier}
          key={reasonCode.objectIdentifier}
          display={`${reasonCode.name}`}
        /&gt;
      ));
  const remarkCodes = this.props.remarkCodeset.map(remarkCode =&gt; (
        &lt;Select.Option
          value={remarkCode.objectIdentifier}
          key={remarkCode.objectIdentifier}
          display={`${remarkCode.name}`}
        /&gt;
      ));
  const otherCodes = this.props.otherCodeset.map(otherCode =&gt; (
        &lt;Select.Option
          value={otherCode.objectIdentifier}
          key={otherCode.objectIdentifier}
          display={`${otherCode.name}`}
        /&gt;
      ));
  return (
        &lt;ContentContainer fill&gt;
          &lt;Spacer marginTop="none" marginBottom="large+1" marginLeft="none" marginRight="none" paddingTop="large+2" paddingBottom="none" paddingLeft="large+2" paddingRight="large+2"&gt;
            &lt;Fieldset legend="Code sets"&gt;
              &lt;Grid&gt;
                &lt;Grid.Row&gt;
                  &lt;Grid.Column tiny={3}&gt;
                    &lt;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]}&gt;
                      {reasonCodes}
                    &lt;/SelectField&gt;
                  &lt;/Grid.Column&gt;
                &lt;/Grid.Row&gt;
                &lt;Grid.Row&gt;
                  &lt;Grid.Column tiny={3}&gt;
                    &lt;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]}&gt;
                      {remarkCodes}
                    &lt;/SelectField&gt;
                  &lt;/Grid.Column&gt;
                &lt;/Grid.Row&gt;
                &lt;Grid.Row&gt;
                  &lt;Grid.Column tiny={3}&gt;
                    &lt;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]}&gt;
                      {otherCodes}
                    &lt;/SelectField&gt;
  &lt;/Grid.Column&gt;
                &lt;/Grid.Row&gt;
  &lt;/Grid&gt;
  
  &lt;/Fieldset&gt;
          &lt;/Spacer&gt;
          &lt;ActionFooter
            className={cx(['action-header-footer-color'])}
            end={(
              &lt;React.Fragment&gt;
                &lt;Spacer isInlineBlock marginRight="medium"&gt;
                  &lt;Button text="Save" onClick={this.validateOnSave} /&gt;
                &lt;/Spacer&gt;
              &lt;/React.Fragment&gt;
            )}
          /&gt;
        &lt;/ContentContainer&gt;
      );
    }
  }
  
  OptionsView.propTypes = propTypes;
  
  export default injectIntl(OptionsView);
  </code></pre>
  
  <p><strong>OptionsView.test</strong></p>
  
  <pre><code>describe('RemittanceOptions View', () =&gt; {
  let defaultProps = {...defined...}
  beforeAll(() =&gt; {  
      Object.defineProperty(window, "matchMedia", {
        value: jest.fn(() =&gt; { 
          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', () =&gt; {
      const wrapper = mountWithIntl(&lt;OptionsView
        {...defaultProps}
      /&gt;); 
      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]);
    });
  });
  </code></pre>
  
  <p><strong>Ultimate goal is to test two cases when save is clicked:</strong></p>
  
  <ol>
  <li><p>When state.codeSelectionIsInvalid: [false,false,true]</p></li>
  <li><p>When state.codeSelectionIsInvalid: [false,false,false]</p></li>
  </ol>
  
  <p>Where am I going wrong here. Any help is appreciated!</p>