Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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!

like image 745
babybear Avatar asked Feb 27 '20 05:02

babybear


People also ask

Why can't I Spy the fetch property in jest?

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.

What is a spy in jest?

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.

What is mocking in jest?

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.

Is it possible to spy on the class field arrow function?

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.


1 Answers

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

like image 151
babybear Avatar answered Oct 19 '22 17:10

babybear