5

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.

1
  • I would recommend that you use URL.createObjectURL instead. but just don't forget to revoke it when you no longer need it any longer Commented May 3, 2020 at 16:13

2 Answers 2

7

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');
Sign up to request clarification or add additional context in comments.

2 Comments

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?
onload is just listener for load event that is triggered by FileReader, 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.
2

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)

1 Comment

Not the best, but elegant, maintainable, and easy to understand.

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.