【问题标题】:expect(...).toHaveBeenCalled() fails even though the React component method is called即使调用了 React 组件方法,expect(...).toHaveBeenCalled() 也会失败
【发布时间】:2019-08-09 04:54:28
【问题描述】:

我正在尝试测试是否使用酶和玩笑来调用 React 组件方法。当<section> 元素变得没有焦点(模糊)时,应该调用该函数。该组件连接到 Redux 存储,但它也按名称导出,以将 Redux 排除在等式之外。以下是该组件的简化版本:

export class Section extends React.Component {
  constructor(props) {
    super(props)
    this.formRef = React.createRef();
    this.submitForm = this.submitForm.bind(this);
    this.state = {
      formSubmitted: false
    }
  }

  submitForm() {
    console.log("form submitted");
    this.setState({ formSubmitted: true })
    if (this.formRef && this.formRef.current) {
      this.formRef.current.submit();
    }
  }

  render() {
    return (
      <section onBlur={this.submitForm}>
        <form ref={this.formRef} action={url} method='post'>
          <input type="text" name="something" />
        </form>
      </section>
    );
  }
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Section);

我已经为spyOn 部分尝试了各种组合,因为它似乎是问题的根源。都失败了。

import React from 'react';
import { shallow } from 'enzyme';
import { Section } from './Section';

describe('Section', () => {
  it('should submit form on blur', () => {
    const wrapper = shallow(<Section content={{ body: [] }} />);
    const spy = spyOn(wrapper.instance(), 'submitForm');
    const spy2 = spyOn(Section.prototype, 'submitForm');
    const spy3 = jest.spyOn(wrapper.instance(), 'submitForm');
    const spy4 = jest.spyOn(Section.prototype, 'submitForm');

    wrapper.find('section').simulate('blur');
    expect(wrapper.state().formSubmitted).toEqual(true);
    expect(spy).toHaveBeenCalled();
    expect(spy2).toHaveBeenCalled();
    expect(spy3).toHaveBeenCalled();
    expect(spy4).toHaveBeenCalled();
  })
})

我给组件一个状态,除了测试expect(...).toHaveBeenCalled之外,还测试了它,以验证函数是否被实际调用,似乎是这样。

console.log('form submitted') 出现在控制台中。

测试expect(wrapper.state().formSubmitted).toEqual(true); 通过,这表明正在调用正确的函数。但是,我不想仅仅为了测试而有不必要的状态。刚刚添加状态以断言正在调用“submitForm”方法。

断言expect(...).toHaveBeenCalled() 全部失败,错误为Expected spy to have been called, but it was not calledExpected mock function to have been called, but it was not called.

【问题讨论】:

    标签: javascript reactjs redux jestjs enzyme


    【解决方案1】:

    有趣的是,这个问题深入探讨了 JavaScript 的一些更不寻常的方面。


    会发生什么

    submitForm最初定义为prototype method

    class Section extends React.Component {
      ...
      submitForm() { ... }
      ...
    }
    

    ...意味着您将像这样创建spy

    jest.spyOn(Section.prototype, 'submitForm');
    

    ...但随后它在constructor 中被重新定义为instance property

    this.submitForm = this.submitForm.bind(this);
    

    ...意味着您将现在像这样创建spy

    jest.spyOn(wrapper.instance(), 'submitForm');
    

    ...但是那个 still 不起作用,因为onBlurrender 期间直接 绑定到this.submitForm

    <section onBlur={this.submitForm}>
    

    因此,当前编写代码的方式实际上使监视submitForm 成为不可能,因为创建spy 需要instance,但instance 在组件渲染和onBlur 之前不可用在渲染期间直接绑定到this.submitForm


    解决方案

    onBlur更改为一个调用this.submitForm的函数,这样每当onBlur触发时,它就会调用this.submitForm当前值: p>

    render() {
      return (
        <section onBlur={() => this.submitForm()}>
          <form ref={this.formRef} action={url} method='post'>
            <input type="text" name="something" />
          </form>
        </section>
      );
    }
    

    ...那么当您将submitForm 替换为instance 上的spy 时,spy 将在onBlur 触发时被调用:

    describe('Section', () => {
      it('should submit form on blur', () => {
        const wrapper = shallow(<Section content={{ body: [] }} />);
        const spy = jest.spyOn(wrapper.instance(), 'submitForm');
    
        wrapper.find('section').simulate('blur');
        expect(wrapper.state().formSubmitted).toEqual(true);
        expect(spy).toHaveBeenCalled();  // Success!
      })
    })
    

    【讨论】:

    • 在每次渲染上创建箭头函数可能会导致性能问题。
    • @AndreiDotsenko 根据官方React doc:在渲染方法中使用箭头函数可以吗? "Generally speaking, yes, it is OK"。如果您遇到性能问题,请务必删除箭头功能,但性能成本通常非常小。
    【解决方案2】:

    实际上你想验证当Section失去焦点时表单的submit()是否被调用,对吧?所以你不需要验证是否调用了内部方法——它不能确保表单是否被提交。在重构的情况下也会导致假阴性结果(内部方法被重命名,一切正常,但测试失败)。

    您可以改为模拟 ref 对象。然后您将能够验证是否已提交表单

    it('should submit form on blur', () => {
      const wrapper = shallow(<Section content={{ body: [] }} />);
      wrapper.instance().formRef.current = { submit: jest.fn()};
      wrapper.find('section').simulate('blur');
      expect(wrapper.instance().formRef.current.submit).toHaveBeenCalled();
    })
    

    当然测试还依赖于组件的内部结构(包含 ref 的属性)。但是这种方式我相信测试更......说可靠。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-10-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多