DEV Community

Edwin Wong
Edwin Wong

Posted on

How to mock function that called inside module

Problem

Iets say you have the file

// file.js

export function b() {
    return 'B'
}

export function a() {
    return b()
}

module.exports = {
    a,
    b,
}
Enter fullscreen mode Exit fullscreen mode

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'
})
Enter fullscreen mode Exit fullscreen mode

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'
})
Enter fullscreen mode Exit fullscreen mode

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)],    
    ...
    ...
}
Enter fullscreen mode Exit fullscreen mode

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()
}
Enter fullscreen mode Exit fullscreen mode

Solution 1

Move function b to another file.

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

export function a() {
    return b()
}
Enter fullscreen mode Exit fullscreen mode

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,
}
Enter fullscreen mode Exit fullscreen mode

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
    }
}
Enter fullscreen mode Exit fullscreen mode

Both are achive the same result and same principle.

Top comments (2)

Collapse
 
jackmellis profile image
Jack

Solution 3 is to use a dependency injection or composition pattern to give you more control over your dependencies ๐Ÿ‘€

Collapse
 
edwinwong90 profile image
Edwin Wong

Yeah~ true.