【问题标题】:Jest: Cannot spy the property because it is not a function; undefined given instead开玩笑:无法窥探该属性,因为它不是函数;给定的未定义
【发布时间】:2020-06-11 02:05:08
【问题描述】:

我正在尝试为一个简单的 React 组件编写一个 Jest 测试,以确认在我模拟点击时调用了一个函数。

但是,当我使用 spyOn 方法时,我不断收到 TypeError: Cannot read property 'validateOnSave' of undefined. 我的代码如下所示:

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

最终目标是在点击保存时测试两种情况:

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

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

我哪里错了。任何帮助表示赞赏!

【问题讨论】:

    标签: javascript reactjs testing jestjs enzyme


    【解决方案1】:

    经过几个小时的调试,发现实例没有绑定任何方法。由于是连接组件,使用shallowWithIntl()dive()解决了错误。

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

    【讨论】:

      猜你喜欢
      • 2021-07-05
      • 2020-09-04
      • 1970-01-01
      • 2020-06-26
      • 2021-11-02
      • 1970-01-01
      • 1970-01-01
      • 2023-03-03
      • 2020-11-19
      相关资源
      最近更新 更多