开玩笑:无法窥探该属性,因为它不是函数;给定的未定义

Posted

技术标签:

【中文标题】开玩笑:无法窥探该属性,因为它不是函数;给定的未定义【英文标题】: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]);
  );
);

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

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

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

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

【问题讨论】:

【参考方案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();
  );

【讨论】:

以上是关于开玩笑:无法窥探该属性,因为它不是函数;给定的未定义的主要内容,如果未能解决你的问题,请参考以下文章

无法读取未定义和未处理的承诺拒绝的属性“捕获”

如何模拟 axios API 调用?开玩笑

无法窥探原始值;在nestJS中给定的未定义

GetOrgChart:尚未定义父节点的节点

Spring HATEOAS RepresentationModel,无法设置属性链接,因为没有设置器,没有枯萎,它不是持久性构造函数的一部分

Angular - 无法绑定属性,因为它不是“div”的已知属性