node.jsunit-testingmockingjestjsspy

Use `jest.spyOn` on an exported function from a Node module


In Jest, to spy (and optionally mock the implementation) on a method, we do the following:

const childProcess = require('child_process');
const spySpawnSync = jest.spyOn(childProcess, 'spawnSync').mockImplementation();

This allows us to use spySpawnSync to check what arguments it was last called with, like so:

expect(spySpawnSync).lastCalledWith('ls');

However, this is not possible with Node modules that export a function, such as with the execa package.

I tried each the following, but none of them spy or mock the function:

// Error: `Cannot spy the undefined property because it is not a function; undefined given instead`
jest.spyOn(execa);

// Error: `Cannot spyOn on a primitive value; string given`
jest.spyOn('execa');

// Error: If using `global.execa = require('execa')`, then does nothing. Otherwise, `Cannot spy the execa property because it is not a function; undefined given instead`.
jest.spyOn(global, 'execa');

Therefore, is there any way to spy on modules that export a function, such as execa in the given example?


Solution

  • I had the exact same need and problem with execa, and here's how I made it work:

    import execa from 'execa'
    
    
    jest.mock('execa', () => jest.fn())
    
    test('it calls execa', () => {
      runSomething()
      expect(execa).toHaveBeenCalled()
    })
    
    

    So basically, since the imported module is the function itself, what you do is mock the whole module with jest.mock, and simply return a Jest mock function as its replacement.

    Since jest.fn() is what jest.spyOn() relies on under the hood, you benefit from the same assertion methods in your tests :)