5

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?

Gary
  • 3,891
  • 8
  • 38
  • 60

2 Answers2

0

We can try mocking the module at the start of the test itself, say before the describe block, like below:

jest.mock('execa’, () => ({
  Method1: jest.fn(),
  Method2: jest.fn().mockReturnValue(…some mock value to be returned)
}));

here Method1 and Method2 are some methods of execa which you would like to mock and test, or if you are consuming them.

Else, you can just mock like below and it should work:

jest.mock('execa');
Anuradha Kumari
  • 703
  • 5
  • 9
0

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 :)

Olivier Lance
  • 1,738
  • 17
  • 30
  • @oliver-lance, I like your answer. It works for my use case when I just want to verify that the module function has been called. However, I was trying to mock the response for each call to `execa`, when testing a user defined function that calls it multiple times and takes different paths based on the response of say the second call. This seems to be difficult. I'm trying to determine how best to proceed. I'm thinking of creating a class with methods that wrap each type of call, based on parameters. It's relatively easy to mock each method of the class. – Delvin Defoe Nov 05 '21 at 02:26
  • Could you maybe use a mock implementation for `execa` instead of the empty mock function I used? Like this: `jest.fn(cmd => doSomething())` You would of course have a more complex body, but at least you can control the return value of your mock function depending on the executable path – Olivier Lance Nov 16 '21 at 00:08