6

I'm trying to spy on a function that's called by another function, both of which reside in an external file and imported.

Funcs.spec.js:

import * as Funcs from './Funcs'
describe('funcA', () => {
    it('calls funcB', () => {
        jest.spyOn(Funcs, 'funcB')
        Funcs.funcA()
        expect(Funcs.funcB).toHaveBeenCalled()
    }
}

Funcs.js:

export const funcA = () => {
    funcB()
}
export const funcB = () => {}

For some reason the spy is not respected in scope of Funcs.js. What can I do to spy on funcB so I know funcA has called it?

Canta
  • 1,480
  • 1
  • 13
  • 26
Dean James
  • 2,491
  • 4
  • 21
  • 29

2 Answers2

8

Only methods can be spied. There is no way to spy on funcB if it's called directly like funcB() within same module.

In order for exported function to be spied or mocked, funcA and funcB should reside in different modules.

This allows to spy on funcB in transpiled ES module (module object is read-only in native ESM):

import { funcB } from './b';

export const funcA = () => {
    funcB()
}

Due to that module imports are representations of modules, this is transpiled to:

var _b = require('./b');

var funcA = exports.funcA = function funcA() {
    (0, _b.funcB)();
};

Where funcB method is tied to _b module object, so it's possible to spy on it.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • This is correct, but incomplete. What the OP asks (to test functions exported in the same file) can be achieved without splitting into different files/modules. If you are curious to know how check my [answer](https://stackoverflow.com/a/50856001/220272). – a--m Jun 14 '18 at 12:27
  • @a--m *without splitting into different files/modules* - this wasn't listed as a requirement. The code needs to be refactored to be more testable, I just suggested an idiomatic way to improve the testability. This is a trade-off that depends on how crucial is to spy on funcB; could be unnecessary. Yes, it's possible with CJS modules like you suggested, but it seems a bit too much IMO. Since the context for this code is unknown, there's no guarantee that CJS modules are appropriate or supported in the application (they also don't support tree-shaking). – Estus Flask Jun 14 '18 at 16:35
  • Understood. I agree with your arguments about splitting as an improvement for testing as a generic rule. But due to the fact that the OP states that *both of which reside in an external file and imported* I thought that including a solution for this specific requirement its also important. As matter of fact what we are discussing here is also stated on the jest issue I referred ;) – a--m Jun 14 '18 at 16:41
5

The problem you describe is referenced on a jest issue.

A possible solution to your problem (if you want to keep the functions inside the same file) is to use CommonJS, consider the following example:

fns.js

exports.funcA = () => {
  exports.funcB();
};
exports.funcB = () => {};

fns.spec.js

const fns = require("./fns");

describe("funcA", () => {
  it("calls funcB", () => {
    fns.funcB = jest.fn();
    fns.funcA();
    expect(fns.funcB).toBeCalled();
  });
});
a--m
  • 4,716
  • 1
  • 39
  • 59