11

I have simple service that I need unit tested using jest:

the crux of the code is this:

 domtoimage.toBlob(node, {filter: filter})
    .then(function (blob) {
      FileSaver.saveAs(blob, fileName);
 });

I have wrote my unit test module as such:

import FileSaver from "file-saver";
import domtoimage from "dom-to-image";


jest.mock('dom-to-image', () => {
  return {
    toBlob: (arg)=>{
      let promise = new Promise((resolve, reject) => {
        resolve('myblob')
      });
      return promise;
    }
  }
});
jest.mock('file-saver', ()=>{
  return {
    saveAs: (blob, filename) =>{
      return filename;
    }
  }
});

And in my test, I have the following spy set up

const spy = jest.spyOn(FileSaver, 'saveAs');

and calling my in-test function.

however, the expect statement: expect(spy).toBeCalled() returns false:

expect(jest.fn()).toBeCalled()

However, in webstorm, when I debug the unit test, I can clearly see that my mocked function is being called (the breakpoint is reached inside function).

What am i missing?

Nate
  • 1,630
  • 2
  • 24
  • 41
  • So I am able to figure out this is due to the promise inside `toBlob`...i need to wait for it to resolve (or force resolve) before checking spy – Nate Jun 12 '18 at 18:15

2 Answers2

10

Suggestion 1

Maybe spyOn and module mocks don't play well together. You could try using a jest.fn() directly inside the module mock like so

jest.mock('file-saver', ()=>{
  return {
    saveAs: jest.fn((blob, filename) => {
      return filename;
    })
  }
});

and then

expect(FileSaver.saveAs).toBeCalled()

Remember to call jest.clearAllMocks() or similar between tests.

Suggestion 2 I've had issues with jest.mock working in unexpected ways with the jest module cache, especially when working with singleton imports. Maybe you have this issue. if file-saver and dom-to-image don't have any state initialized or side-effects on import time you should be able to swap jest.mock out for overrides of the functions you need to mock.

beforeEach(() => {
  FileSaver.saveAs = jest.fn(...);
  domtoimage.toBlob = jest.fn(...);
})
Red Mercury
  • 3,971
  • 1
  • 26
  • 32
0

So for those of you wondering something similar...my issue (as I suspected) was the promise in domtoimage.toBlob(node, {filter: filter}).then()

essentially, the promise was resolving after my expect was called.

in order to solve it, I placed my expect behind a setTimeout, thus forcing it to fire after the promise is resolved.

 DownloadImage('x', 'name');
  //small timeout to resolve the promise inside downldimage function
  setTimeout(()=>{
    expect(FileSaver.saveAs).toHaveBeenCalledWith('myblob', fileName);
    done();
  }, 100);
Nate
  • 1,630
  • 2
  • 24
  • 41
  • Thanks! I suspected the promise, too. However, having arbitrary timeouts all over my test code seems a bad workaround. Is there a cleaner way to do this? – morgler Nov 24 '20 at 10:16
  • @morgler A clean way to do this would be to await on your functions and make sure your functions don't use setTimeout, if at all possible (just ensure that everything in your code is a promise chain). `const res = await domtoimage.toBlob(..); expect(res).resolves.toEqual(...something); expect(FileSaver.saveAs).toHaveBeenCalledWith(...)` More details here, for example: https://www.leighhalliday.com/mock-fetch-jest – MKouhosei Mar 22 '21 at 06:42