4

SimpleDialog.jsx

const [imagePreview, setImagePreview] = React.useState(null);

const handleChangeImage = event => {
    let reader = new FileReader();
    let file = event.target.files[0];

    reader.onload = event => {
        console.log(event);

        setImagePreview(event.target.result);
    };

    reader.readAsDataURL(file);
};

return (
    <div>
        <input
            accept="image/*"
            id="contained-button-file"
            multiple
            type="file"
            style={{ display: 'none' }}
            onChange={handleChangeImage}
        />

        <img id="preview" src={imagePreview} />
    </div>
);

SimpleDialog.test.js

it('should change image src', () => {
    const event = {
        target: {
            files: [
                {
                    name: 'image.png',
                    size: 50000,
                    type: 'image/png'
                }
            ]
        }
    };

    let spy = jest
        .spyOn(FileReader.prototype, 'onload')
        .mockImplementation(() => null);

    wrapper.find('input[type="file"]').simulate('change', event);

    expect(spy).toHaveBeenCalled();

    expect(wrapper.find('#preview').prop('src')).not.toBeNull();
});

When running the test it gives me the error TypeError: Illegal invocation.

Anyone who can help me with this unit test? I Just want to simulate on change if the src of an image has value or not.

aldrin27
  • 3,407
  • 3
  • 29
  • 43
  • I would recommend that you use [URL.createObjectURL](https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL) instead. but just don't forget to [revoke](https://developer.mozilla.org/en-US/docs/Web/API/URL/revokeObjectURL) it when you no longer need it any longer – Endless May 03 '20 at 16:13

2 Answers2

5

The cause of the error is that onload is defined as property descriptor and assigning it to FileReader.prototype which is done by spyOn isn't supported.

There's no reason to mock onload because it's assigned in tested code and needs to be tested.

The straightforward way is to not patch JSDOM FileReader implementation but stub it entirely:

jest.spyOn(global, 'FileReader').mockImplementation(function () {
    this.readAsDataURL = jest.fn();
});

wrapper.find('input[type="file"]').simulate('change', event);


let reader = FileReader.mock.instances[0];
expect(reader.readAsDataURL).toHaveBeenCalledWith(...);
expect(reader.onload).toBe(expect.any(Function));

expect(wrapper.find('#preview').prop('src')).toBeNull();

reader.onload({ target: { result: 'foo' } });

expect(wrapper.find('#preview').prop('src')).toBe('foo');
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • Thank you @Estus Flask. I'm a bit confused regarding the `onload` event. So all methods and events here were mocked? Can you give me a reference? – aldrin27 May 03 '20 at 15:39
  • 1
    onload is just listener for `load` event that is triggered by FileReader, https://developer.mozilla.org/en-US/docs/Web/API/FileReader/onload . Here it's tested that a listener was set by the component and that it produces necessary side effects. – Estus Flask May 03 '20 at 15:50
0

Had the same issue with checking some side effect that should be invoked on FileReader.onload, so I just ended up setting a short pause after triggering the event (I'm using enzyme + jest). Setting the timeout is probably not the best solution here, but it was the only thing that worked.

const pauseFor = milliseconds => new Promise(resolve => setTimeout(resolve, milliseconds));
...
wrapper.find('.upload-box').simulate('drop', someMockedDropEvent);
// set pause was the only way to make reader.onload to fire
await pauseFor(100);
expect(something).toEqual(something)
Alexey
  • 1,914
  • 16
  • 13