Jest — How To Mock a Function Call Inside a Module

Mocking function calls within a module

Vitali Zaidman
Welldone Software
3 min readMay 3, 2021

--

JEST logo

Let’s say you have the file:

// f.jsexport function b(){
return 'b';
}
export function a(){
return b();
}

If you want to mock b to test a, well… It is not as easy as it seems to be.

The Naive Approach

What I initially tried to do was:

test('a', () => {
const f = require('./f');
jest.spyOn(f, 'b').mockReturnValue('c'); expect(f.a()).toBe('c');
// FAILED!
// expected 'c' got 'b'
})

Nope… It won’t work.

That is because the exported b is mocked indeed, but it’s not the same b as the one that is called by a inside the module.

Solution 1 — Splitting The Module Into Different Files

If you move b to its own file:

// b.jsexport function b(){
return 'b';
}
// f.jsimport {b} from './b';export function a(){
return b();
}

Then the test will pass:

test('a', () => {
const b = require('./b');
const f = require('./f');
jest.spyOn(b, 'b').mockReturnValue('c'); expect(f.a()).toBe('c');
//PASSED!
})

This looks like the cleanest solution, but what if you want to keep your functions in the same file?

Solution 2 — Calling The Mocked Function Using Exports

Add exports. before calling the function

// f.jsexport function b(){
return 'b';
}
export function a(){
return exports.b();
}

And then the test will just pass:

test('a', () => {
const f = require('./f');
jest.spyOn(f, 'b').mockReturnValue('c'); expect(f.a()).toBe('c');
//PASSED!
})

I really like this solution because you change only something insignificant in the tested file. Moreover, even this small change can be avoided:

You can use the library babel-plugin-explicit-exports-references to add exports. to all functions within the same module programmatically.

Notice that the library is very fresh and has a very small audience.
Use it with caution in terms of security.

A Sub-Optimal Solution Worth Mentioning — Exporting a Namespace Object

You can create a namespace that you export as the default object and call b using the namespace.

This way, when you call jest.mock it will replace the b function on the namespace object.

// f.jsconst f = {
b(){
return 'b';
},
a(){
return f.b();
}
};
export default f;

And then the test will pass:

test('a', () => {
const f = require('./f');
jest.spyOn(f, 'b').mockReturnValue('c'); expect(f.a()).toBe('c');
//PASSED!
})

But then you will need to import it as a default import without being able to break it into named exports.

// won't work:
// import {a} from './f';
import f from './f';...f.a();

Which is kinda ugly.

Another Sub-Optimal Solution— Using a Re-Wire Library

The library babel-plugin-rewire is an intrusive library that changes what’s inside modules.

It doesn’t seem to be a very maintained library and it actually didn’t work for me because it doesn’t support TypeScript, but here is roughly how it is supposed to work —

You don’t need to change anything on the file that you are testing.

// f.jsexport function b(){
return 'b';
}
export function a(){
return b();
}

Rewire the b function in the test:

import {a, __set__} from './f';test('a', () => {
const f = require('./f');
// This rewires b to return 'c'
__set__(
'b', () => 'c');
expect(f.a()).toBe('c');
// PASSED!
})

Happy Testing :)

--

--