15

I am working on a wrapper component for smoothly loading images in React. I use enzyme with mocha, chai and sinon to unit test my component. In the test here, I am trying to test that the:

  1. component's state is updated when the image has loaded

  2. the onLoad instance method on the component was called

const wrapper = shallow( );

const onLoad = wrapper.find('img').props().onLoad;
const onLoadSpy = sinon.spy(onLoad); wrapper.update();
const status = wrapper.state().status;
expect(onLoadSpy).to.have.been.called;
expect(status).to.equal('LOADED');

I find that neither the update to the state is reflected by enzyme or the call count of the onLoad spy is updated. This is the corresponding code for the test:

export default class Image extends Component {
  constructor(props) {
    super(props);
    if (props.src != null && typeof props.src === 'string') {
      this.state = {
        status: LOADING,
      };
    } else {
      this.state = {
        status: PENDING,
      };
    }
    this.onLoad = this.onLoad.bind(this);
  }

  onLoad() {
    this.setState({
      status: LOADED,
    });
  }

  render() {
    //lots of code above the part we care about
    const defaultImageStyle = style({
      opacity: 0,
      transisition: 'opacity 150ms ease',
    });

    const loadedImageStyle = style({
      opacity: 1,
    });

    let imageStyle = defaultImageStyle;

    if (this.state.status === LOADED) {
      imageStyle = merge(defaultImageStyle, loadedImageStyle);
    } else {
      imageStyle = defaultImageStyle;
    }


    let image;
    if (alt != null) {
      image = (<img
        className={imageStyle}
        src={src}
        width={width}
        height={height}
        alt={alt}
        onLoad={this.onLoad}
      />);
    } else {
      image = (<img
        className={imageStyle}
        src={src}
        width={width}
        height={height}
        role="presentation"
        onLoad={this.onLoad}
      />);
    }

    let statusIndicator = null;
    if (this.state.status === LOADING) {
      statusIndicator = (<div className={loadingStyle}></div>);
    }

    return (<div className={wrapperStyle}>
      {statusIndicator}
      {image}
    </div>);

    }}

To take a look at the full code for better context:

2 Answers 2

16

One can test this without relying on sinon. By expecting that the onLoad and onFire event listeners are invoked,the tests check if the img fires the load and error events.

Instead,simulate img's events using enzyme and check that the appropriate state transition occurs:

it('has a state of LOADED if a good src prop is supplied', () => {
  const wrapper = shallow(<Image 
    src="anyString.jpg"
    width={300}
    height={300}
  />);

  const img = wrapper.find('img');
  img.simulate('load');
  const status = wrapper.state().status;
  expect(status).to.equal('LOADED');
});

This also eliminates the need to mount the component. The updated tests can be found here.

Sign up to request clarification or add additional context in comments.

2 Comments

What if the onLoad function returns a promise? This is not working in that case.
@saran3h Not a detailed answer: Consider using jest to mock the function from which you receive the promise in onLoad. If this is part of an external library or directly inside the function, I recommend extracting it into a function or method that you can mock.
0

The main issue I see with this approach is that the state is an internal thing, and not something that should be known outside the component. Now you are leaking state information ("status" in this case) out into the tests.

Doing this means you are not doing "black-box testing", which is the most valuable type of tests. You are leaking the implementation details of the component. In other words "Encapsulation" should be highly considered.

There are perhaps better ways to test this. You could for instance export a presentational component as well, which takes the parts of the state you need to test as props. Or look for an element that would be rendered when status is "LOADED" with enzyme find method.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.