如何在单元测试中触发FileReader的`onloadend`?

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

我正在尝试使用反应测试库测试 input type=file 组件。

该组件是一个标准的 <input> 元素,具有以下处理图像提交的功能:

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.
}

因为想模拟上传图片,所以就这样测试了:

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);
  });

问题是 setImageSpy 永远不会被调用。 如果我理解正确的话,这是因为 onloadend 永远不会被触发。

如何触发该事件?

根据 expected behaviourreadAsDataURL 模拟应该提供 result 而不是存根。

this.onloadend = onloadend 是朝着错误方向迈出的一步。 onloadend 不应该被嘲笑,因为它是在测试代码中分配的。测试中需要手动调用:

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);