最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to mock FileReader using jest - Stack Overflow

programmeradmin1浏览0评论

I need to mock a function that uses FileReader using jest. Specifically the function readAsBinaryString and onload.

I've created some code:

FileReader.readAsBinaryString = () => mock.mockReturnValue(null);

But it doesn't work. How can I mock FileReader and your functions using jest?

Function to test:

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 = 
  };
},

I need to mock a function that uses FileReader using jest. Specifically the function readAsBinaryString and onload.

I've created some code:

FileReader.readAsBinaryString = () => mock.mockReturnValue(null);

But it doesn't work. How can I mock FileReader and your functions using jest?

Function to test:

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 = 
  };
},
Share Improve this question edited Oct 31, 2019 at 16:15 AjjjHsh asked Oct 31, 2019 at 13:47 AjjjHshAjjjHsh 2532 gold badges6 silver badges13 bronze badges 1
  • You shouldn't read the file as a binaryString, use arrayBuffer or text – Endless Commented Oct 14, 2020 at 16:13
Add a comment  | 

5 Answers 5

Reset to default 6

You can use jest.spyOn(object, methodName, accessType?) to spy on readAsBinaryString method of FileReader. readAsBinaryString is an instance method, not static method of FileReader constructor. Besides, the return value of readAsBinaryString is void. So you can't mock a return value.

E.g.

index.ts:

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

index.spec.ts, we need spy on FileReader.prototype.readAsBinaryString, since it's an instance method.

import { main } from './';

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

Unit test result with 100% coverage:

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

Unit test result with 100% coverage:

 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

Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/58644737

I personally could not get any of the jest.spyOn() approaches to work in my Vue-test-utils setup, jest.spyOn(FileReader.prototype, 'readAsDataURL'); kept generating the following error: Cannot spy the readAsDataURL property because it is not a function; undefined given instead

If it can help anyone else having issues with this, I managed to successfully mock the FileReader prototype using the following:

Object.defineProperty(global, 'FileReader', {
  writable: true,
  value: jest.fn().mockImplementation(() => ({
    readAsDataURL: jest.fn(),
    onLoad: jest.fn()
  })),
})

Then in my test, I was able to test the file input onChange method (which was making use of the FileReader) by mocking the event and triggering it manually like this:

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

I've made some progress:

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

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

The problem is that onload isn't get mocked on real call:

reader.onload = function() {
}

Only when I call

reader.onload()

So I think onload declaration on dummy is wrong.

if you want to call the onload event as a part of readAsText You can use following code

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

Pretty late comment, but for what it's worth, this his how I was able to mock FileReader:

First, I created a function that returned new FileReader() instead of calling it directly.

export function fileReaderWrapper() {
  return new FileReader();
}

Then the code that needs file reader can call that function

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

Now my test can use jest to mock everything I check.. and I no longer have to use a timeout in my test to wait on FileReader to finish reading a file.

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

评论列表(0)

  1. 暂无评论