【问题标题】:How do you use Jest to test img.onerror如何使用 Jest 测试 img.onerror
【发布时间】:2017-11-11 18:13:19
【问题描述】:

我创建了一个 React 组件,用于加载图像并确定图像是否加载成功。

import React from 'react';
import PropTypes from 'prop-types';
import { LOADING, SUCCESS, ERROR } from '../helpers';

class Image extends React.Component {
  static propTypes = {
    onError: PropTypes.func,
    onLoad: PropTypes.func,
    src: PropTypes.string.isRequired,
  }

  static defaultProps = {
    onError: null,
    onLoad: null,
  }

  constructor(props) {
    super(props);
    this.state = { imageStatus: LOADING };
    this.initImage();
  }

  componentDidMount() {
    this.image.onload = this.handleImageLoad;
    this.image.onerror = this.handleImageError;
    this.image.src = this.props.src;
  }

  initImage() {
    this.image = document.createElement('img');
    this.handleImageLoad = this.handleImageLoad.bind(this);
    this.handleImageError = this.handleImageError.bind(this);
  }

  handleImageLoad(ev) {
    this.setState({ imageStatus: SUCCESS });
    if (this.props.onLoad) this.props.onLoad(ev);
  }

  handleImageError(ev) {
    this.setState({ imageStatus: ERROR });
    if (this.props.onError) this.props.onError(ev);
  }

  render() {
    switch (this.state.imageStatus) {
      case LOADING:
        return this.renderLoading();
      case SUCCESS:
        return this.renderSuccess();
      case ERROR:
        return this.renderError();
      default:
        throw new Error('unknown value for this.state.imageStatus');
    }
  }
}

export default Image;

我正在尝试使用 Jest + Enzyme 创建一个测试来测试图像何时无法加载。

  it('should call any passed in onError after an image load error', () => {
    const onError = jest.fn();
    mount(<Image {...props} src="crap.junk"} onError={onError} />);
    expect(onError).toHaveBeenCalled();
  });

无论我做什么,Jest 总能找到成功渲染图像的方法。即使将 src 设置为 false 仍然会以某种方式呈现图像。有谁知道你怎么能强迫开玩笑使图像加载失败?

【问题讨论】:

    标签: unit-testing reactjs jestjs enzyme


    【解决方案1】:

    你可以模拟document.createElement:

     it('should call any passed in onError after an image load error', () => {
        const img = {}
        global.document.createElement = (type) => type === 'img' ? img : null
        const onError = jest.fn();
        mount(<Image {...props} src="crap.junk"} onError={onError} />);
        img.onload()
        expect(onError).toHaveBeenCalled();
      });
    

    【讨论】:

    • 我认为你在做某事,但在模拟 document.createElement 时存在问题。我的 Image 函数还根据参数创建 span 和 div,因此返回 null 会导致测试崩溃。现在我正在测试是否可以添加任何属性。它不工作。据我所知:gist.github.com/mrbinky3000/567b532928f456ee74e55ebe959c66ec
    • 我放弃了。我只是决定使用 wrapper.instance() 然后直接调用 instance.image.onerror() 。我突然想到我过度测试了。我必须相信 HTMLImageElement 会在出现图像错误时触发 onerror。如果不是,则 Web 浏览器的源代码存在巨大错误。赔率:零。所以我只是手动触发错误来模拟错误。结案。不过,谢谢。
    【解决方案2】:

    由于在幕后,document.createElement('img') 等同于 new Image(),我们可以模拟部分 jsdom(由 Jest 使用)实现 Image 来实现您的目标。

    (为简单起见,我已将您的组件重命名为 ImageComponent

    describe('Callbacks', () => {
      const LOAD_FAILURE_SRC = 'LOAD_FAILURE_SRC';
      const LOAD_SUCCESS_SRC = 'LOAD_SUCCESS_SRC';
    
      beforeAll(() => {
        // Mocking Image.prototype.src to call the onload or onerror
        // callbacks depending on the src passed to it
        Object.defineProperty(global.Image.prototype, 'src', {
          // Define the property setter
          set(src) {
            if (src === LOAD_FAILURE_SRC) {
              // Call with setTimeout to simulate async loading
              setTimeout(() => this.onerror(new Error('mocked error')));
            } else if (src === LOAD_SUCCESS_SRC) {
              setTimeout(() => this.onload());
            }
          },
        });
      });
    
      it('Calls onError when passed bad src', done => {
        const onError = ev => {
          expect(ev).toBeInstanceOf(Error);
    
          // Indicate to Jest that the test has finished
          done();
        };
    
        mount(<ImageComponent onError={onError} src={LOAD_FAILURE_SRC} />);
      });
    });
    

    onerror/onload 从未像在浏览器中那样被调用的原因在此jsdom issue 中进行了解释。在同一 issue thread 中提供了一个会影响所有测试的替代修复。

    【讨论】:

    • 这对我有用。但是,似乎没有办法在测试套件结束时取消设置 Object.defineProperty,将 Image.prototype.src 恢复到原来的样子。
    • 如果你查看这个答案的编辑历史,你会发现我曾经有这样的清理逻辑,但我删除了它,因为我确认修改后的原型只存在于某个特定的范围内范围(describe 块,或文件范围,我不记得了),然后恢复。如果您想要旧的包含还原的代码,请从答案历史记录中获取。
    • @Chris 它存在于同一个测试套件(即文件)中。如果您在同一个文件中有多个依赖 Image.src 以按预期工作的测试,则会出现问题。每个测试都应自行清理,以免影响其他测试恕我直言。
    • 根据您要测试的内容,返回给定的src 也可能有意义:Object.defineProperty(global.Image.prototype, 'src', { set(src) { this._src = src; setTimeout(() =&gt; this.onload &amp;&amp; this.onload()) }, get() { return this._src; }
    【解决方案3】:

    我使用 jest + Enzyme。我只是手动调用 onError 方法,如下所示:

    function Avatar (props) {
        const handleError = (e) => {
            e.target.src = getDefaultSrc()
        }
    
        const getDefaultSrc = () => {
            return `/default.png`
        }
    
        const { user: { avatar } } = props
        return <img className='avatar' src={avatar} onError={handleError} />
    }
    
    // avatar.test.js
    it('should show default image', () => {
        const wrapper = mount(<Avatar {...props} />)
        const handleError = wrapper.find('.avatar').prop('onError')
        expect(typeof handleError).toBe('function')
        const fakeE = {
            target: {
                src: ''
            }
        }
        
        handleError(fakeE)
        expect(fakeE.target.src).toBe('/default.png')
    })
    

    【讨论】:

      【解决方案4】:

      如果您使用的是@testing-library/react,则可以使用fireEvent.error 触发img 上的错误:

      const onError = jest.fn();
      render(<Image alt="test" src="crap.junk"} onError={onError} />);
      fireEvent.error(screen.getByAltText("test"));
      expect(onError).toHaveBeenCalled();
      

      【讨论】:

        猜你喜欢
        • 2020-06-14
        • 2020-06-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-11-15
        • 2015-10-12
        • 2021-02-14
        相关资源
        最近更新 更多