Edwin Wong

Coffee addict · Thinker · Software Engineer

How to mock function that called inside module

Problem

Lets say you have the file

// file.js

export function b() {
	return 'B'
}

export function a() {
	return b()
}

module.exports = {
	a,
	b,
}

function a is calling function b internally. To mock the function b can be very difficult.

You will probably do something like this in your test

Using jest.mock method

jest.mock('./file', () => {
	const original = jest.requireActual('./file')
	return {
		...orignial,
		b: jest.fn()
	}
})

const f = require('./file')

test('a', () => {
	f.b.mockReturnValue('C')
	
	expect(f.a()).toBe('C')
	// this will failed, it got 'B'
})

Using jest.spyOn method

const f = require('./file')

test('a', () => {
	jest.spyOn(f, 'b').mockReturnValue('C')
	
	expect(f.a()).toBe('C')
	// it sill failed!, it got 'B'
})

This is not a bug, above 2 methods are working fine. The main reason is because the reference point. Once the function is mocked and print it out you will see something like this.

[Function: b] {
	_isMockFunction: true,
	getMockImplementation: [Function (anonymous)],
	mock: [Getter/Setter],
	mockClear: [Function (anonymous)],
	mockReset: [Function (anonymous)],
	mockRestore: [Function (anonymous)],	
	...
	...
}

Now you try print out the function b that called in function a. And, run the test again.

export function a() {
	console.log(b) // it will print [Function: b] (without the mock property)
	return b()
}

Solution 1

Move function b to another file.

// b.js
export function b() {
	return 'B'
}
// file.js
import {b} from "./b"

export function a() {
	return b()
}

In this case, just mock b will do. I believe you know better than me.

Solution 2

Using the same reference point. This could be abit ugly to you codebase. I think is fine for me.

// file.js

export function b() {
	return 'B'
}

export function a() {
	return module.exports.b() // magic here
}

module.exports = {
	a,
	b,
}

Or you can do something like, if this is the way you define your module.

// file.js

module.exports = {
	b: () => {
		return 'B'
	},
	a: () => {
		return this.b() // this magic
	}
}

Both are achive the same result and same principle.