1
votes

I've been struggling over the past couple of weeks with unit testing a file upload react component with jest. Specifically, I'm trying to test whether or not the method onReadAsDataUrl is being called from FileReader in one of my methods. This is an example method I am testing:

loadFinalImage = async (file) => {
  const reader = new FileReader();
  reader.onloadend = () => {
    this.setState({
      imagePreviewUrl: reader.result,
      validCard: true,
    });
  };
  await reader.readAsDataURL(file);
}

This is how I am attempting to mock FileReader and test whether or not onReadAsDataUrl has been called:

it('is a valid image and reader.onReadAsDataUrl was called', () => {
    const file = new Blob(['a'.repeat(1)], { type: 'image/png' });
    wrapper = shallow(<ImageUpload />).dive();
    const wrapperInstance = wrapper.instance();
    const mockReader = jest.fn();
    jest.spyOn('FileReader', () => jest.fn());
    FileReader.mockImplementation(() => { return mockReader });
    const onReadAsDataUrl = jest.spyOn(mockReader, 'readAsDataURL');
    wrapperInstance.loadFinalImage(file);
    expect(onReadAsDataUrl).toHaveBeenCalled();
  });

After I run: yarn jest, I get the following test failure:

Cannot spyOn on a primitive value; string given.

I assume I am getting this error because I am not importing FileReader, but I am not exactly sure how I would import it or mock it because FileReader is an interface. Here is an image of the test failure: enter image description here

I am a bit of a noob with jest, reactjs, and web development, but would love to learn how to conquer this problem. Some resources I have looked at so far are: Unresolved Shopify Mock of FileReader, How to mock a new function in jest, and Mocking FileReader with jasmine.

Any help would be greatly appreciated! Thank you in advance.

2
According to the docs: jestjs.io/docs/en/jest-object#jestspyonobject-methodname, the function signature is: jest.spyOn(object, methodName) Have you tried creating a mock for FileReader in the global and then doing jest.spyOn(global, 'FileReader') instead?Jackyef
I swapped jest.spyOn('FileReader', () => jest.fn()) for jest.spyOn(global, 'FileReader') and the error message changed to this line: FileReader.mockImplementation(() => { return mockReader }). The new error message is: TypeError: _sys.default.mockImplementation is not a functionairvine

2 Answers

2
votes

I personally could not get any of the jest.spyOn() approaches to work.

Using jest.spyOn(FileReader.prototype, 'readAsDataURL') kept generating a Cannot spy the readAsDataURL property because it is not a function; undefined given instead error,

and jest.spyOn(global, "FileReader").mockImplementation(...) returned a Cannot spy the FileReader property because it is not a function; undefined given instead error

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 hope it can help anyone else looking into this.

0
votes

Quite possibly the OP has found an answer by now, but since I was facing pretty much the same problem, here's how I did it - taking input from another SO answer.

I think @Jackyef comment is the right way to go, but I don't think the call to mockImplementation you propose is correct.

In my case, the following turned out to be correct.

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

Worth noting that VSCode highlights a potential refactoring at the anonymous function. It suggests:

class (Anonymous function)
(local function)(): void
This constructor function may be converted to a class declaration.ts(80002)

I'm still relatively new to JS, so I'm afraid I can't explain what this is about, nor what refactoring should be done.