Jir
Jir

Reputation: 3135

How to trigger FileReader's `onloadend` in unit tests?

I'm trying to test an input type=file component, using the react testing-library.

The component is a standard <input> element, with the following function to handle an image submission:

export default function ImageUpload(props) {
  const { image, setImage } = props;
  const handleImageChange = e => {
    e.preventDefault();
    let reader = new FileReader();
    const imageFile = e.target.files[0];
    reader.onloadend = () => {
      const image = reader.result;
      setImage(image);
    };
    reader.readAsDataURL(imageFile);
  };
  // etc.
}

As I wanted to simulate the uploading of an image, I went about testing it this way:

test("ImageUpload shows two buttons after an image has been uploaded", () => {
    const setImageSpy = jest.fn();
    const image = "data:image/jpeg;base64,/9j/4AAQSkZJ//20==";
    const file = new File([image], "chucknorris.jpg", { type: "image/jpeg" });

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

    const { getByTestId } = render(
      <ImageUpload image={image} setImage={setImageSpy} />
    );
    fireEvent.change(getByTestId("ImageUpload"), {
      target: {
        files: [file]
      }
    });
    expect(setImageSpy).toHaveBeenCalledWith(image);  // this fails
    expect(readAsDataURL).toHaveBeenCalledTimes(1);
    expect(readAsDataURL).toHaveBeenCalledWith(file);
  });

The problem is that setImageSpy never gets called. If I understand it correctly, this is because onloadend never gets triggered.

How can I fire that event?

Upvotes: 6

Views: 4162

Answers (1)

Estus Flask
Estus Flask

Reputation: 222474

According to the expected behaviour, readAsDataURL mock is supposed to provide result rather than being a stub.

this.onloadend = onloadend is a step in wrong direction. onloadend shouldn't be mocked because it's assigned in tested code. It needs to be manually called in the test:

jest.spyOn(global, "FileReader")
  .mockImplementation(function() {
    this.readAsDataURL = jest.fn(() => this.result = image);
  });

...

expect(FileReader).toHaveBeenCalledTimes(1);

const reader = FileReader.mock.instances[0];

expect(reader.readAsDataURL).toHaveBeenCalledTimes(1);
expect(reader.readAsDataURL).toHaveBeenCalledWith(file);
expect(reader.onloadend).toEqual(expect.any(Function));

expect(setImageSpy).not.toHaveBeenCalled();

act(() => reader.onloadend());

expect(setImageSpy).toHaveBeenCalledWith(image);

Upvotes: 8

Related Questions