如何使用jest模拟FileReader

5
我需要使用jest模拟一个使用FileReader的函数。 特别是readAsBinaryStringonload这两个函数。
我已经编写了一些代码:
FileReader.readAsBinaryString = () => mock.mockReturnValue(null);

但它不起作用。我该如何使用jest模拟FileReader和您的函数?
需要测试的函数:
handleFileUpload(event) {
  let reader = new FileReader();
  let file = event.target.files[0];

  reader.readAsBinaryString(file);

  reader.onload = () => {
    let base64String = btoa(reader.result);
    this.object.image = 
  };
},

你不应该把文件读成二进制字符串,而应该使用arrayBuffer或text。 - Endless
5个回答

6
你可以使用jest.spyOn(object, methodName, accessType?)来监视FileReaderreadAsBinaryString方法。需要注意的是,readAsBinaryString是一个实例方法,而不是FileReader构造函数的静态方法。另外,readAsBinaryString的返回值是void,因此无法模拟返回值。

例如:

index.ts

export function main() {
  const fr = new FileReader();
  const blob = new Blob();
  fr.readAsBinaryString(blob);
}

index.spec.ts 文件中,我们需要对 FileReader.prototype.readAsBinaryString 进行间谍操作,因为它是一个实例方法。
import { main } from './';

describe('main', () => {
  test('should mock FileReader', () => {
    const readAsBinaryStringSpy = jest.spyOn(FileReader.prototype, 'readAsBinaryString');
    main();
    expect(readAsBinaryStringSpy).toBeCalledWith(new Blob());
  });
});

100%覆盖率的单元测试结果:

PASS  src/stackoverflow/58644737/index.spec.ts
  main
    ✓ should mock FileReader (10ms)

----------|----------|----------|----------|----------|-------------------|
File      |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files |      100 |      100 |      100 |      100 |                   |
 index.ts |      100 |      100 |      100 |      100 |                   |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        4.852s, estimated 9s

Update

index.ts:

export class Component {
  object = {
    image: ''
  };
  handleFileUpload(event) {
    let reader = new FileReader();
    let file = event.target.files[0];

    reader.readAsBinaryString(file);

    reader.onload = () => {
      let base64String = btoa(reader.result as string);
      this.object.image = base64String;
    };

    return reader;
  }
}

index.spec.ts:

import { Component } from './';

const cmp = new Component();

describe('main', () => {
  beforeEach(() => {
    jest.restoreAllMocks();
  });
  test('should test handle file upload correctly', () => {
    const mFile = new File(['go'], 'go.pdf');
    const mEvent = { target: { files: [mFile] } };
    const readAsBinaryStringSpy = jest.spyOn(FileReader.prototype, 'readAsBinaryString');
    const btoaSpy = jest.spyOn(window, 'btoa');
    const reader = cmp.handleFileUpload(mEvent);
    expect(reader).toBeInstanceOf(FileReader);
    if (reader.onload) {
      Object.defineProperty(reader, 'result', { value: 'gogo' });
      const mOnloadEvent = {} as any;
      reader.onload(mOnloadEvent);
      expect(btoaSpy).toBeCalledWith('gogo');
      expect(cmp.object.image).toBe(btoa('gogo'));
    }
    expect(readAsBinaryStringSpy).toBeCalledWith(mFile);
  });
});

100%覆盖率的单元测试结果:

 PASS  src/stackoverflow/58644737/index.spec.ts (7.328s)
  main
    ✓ should test handle file upload correctly (13ms)

----------|----------|----------|----------|----------|-------------------|
File      |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files |      100 |      100 |      100 |      100 |                   |
 index.ts |      100 |      100 |      100 |      100 |                   |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        8.78s

源代码:https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/58644737


谢谢,但我需要模拟FileReader的onload事件,因为我需要从文件中获取base64字符串。 - AjjjHsh
@AjjjHsh 编辑你的问题,添加你想要测试的代码。 - Lin Du
@AjjjHsh 更新答案。 - Lin Du
无法实例化 Component.handleFileUpload。我正在使用 vue.js。handleFileUpload 是一个方法。 - AjjjHsh

1
如果你想在readAsText的一部分中调用onload事件,可以使用以下代码:
const readAsTextMock = jest.fn();
jest.spyOn(global, 'FileReader').mockImplementation(function () {
  const self = this;
  this.readAsText = readAsTextMock.mockImplementation(() => {
    self.onload({ target: { result: "file read result mock" } });
  });
});

这个可以工作,但是 const self = this 是不必要的,因为箭头函数会自动绑定 this。然而,在高层次上使用 function () 是很重要的。 - Bob

1
我个人在我的Vue-test-utils设置中无法使用任何jest.spyOn()方法,jest.spyOn(FileReader.prototype, 'readAsDataURL');一直生成以下错误: Cannot spy the readAsDataURL property because it is not a function; undefined given instead 如果有其他人遇到此问题,可以尝试使用以下内容成功地模拟FileReader原型:
Object.defineProperty(global, 'FileReader', {
  writable: true,
  value: jest.fn().mockImplementation(() => ({
    readAsDataURL: jest.fn(),
    onLoad: jest.fn()
  })),
})

然后在我的测试中,我能够通过模拟事件并手动触发来测试文件输入的onChange方法(该方法利用了FileReader):

const file = {
  size: 1000,
  type: "audio/mp3",
  name: "my-file.mp3"
}
const event = {
  target: {
    files: [file]
  }
}
wrapper.vm.onChange(event)

1
我已经取得了一些进展:

const dummy = {
     readAsBinaryString: jest.fn(),
        onload: function(){
          wrapper.vm.object.image = '...'
        }
     }
   }

window.FileReader = jest.fn(() => dummy)

问题在于 onload 在实际调用时无法被模拟。
reader.onload = function() {
}

只有当我调用时

reader.onload()

所以我认为dummy上的onload声明是错误的。

0
相当晚的评论,但值得一提的是,这是我如何模拟FileReader的方法:
首先,我创建了一个函数,返回新的FileReader()而不是直接调用它。
export function fileReaderWrapper() {
  return new FileReader();
}

那么需要文件读取器的代码可以调用该函数

const reader = fileReaderWrapper();
reader.readAsDataURL(file);
while (reader.readyState !== 2) {
  yield delay(100);
}
const imageData = reader.result;

现在我的测试可以使用jest来模拟我检查的所有内容.. 我不再需要在测试中使用超时等待FileReader完成读取文件。

jest.mock('~/utils/helper', () => ({
  fileReaderWrapper: jest.fn().mockReturnValue({
    readAsDataURL: (file: File) => {
      return;
    },
    readyState: 2,
    result: 'data:image/png;base64,undefined',
  }),
}));

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接