Mocking just one function on a function array using Jest

Viewed 3095

Here is my function

//functions.js
const functions =
{
    add: (a, b) => a + b,
    average: (a, b) => functions.add(a, b) / 2
}


module.exports = functions;

Here goes my test,

jest.mock("./functions"); // this happens automatically with automocking

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

test('calculate the average', () => {

    functions.add.mockImplementation((a, b) => a + b);
    expect(functions.average(2, 2)).toBe(2);

})

I know how to do it on NestJS, at least for the context I am in. I am preparing a Udemy tutorial, and I would like to make a simple example. I would like to be able to double check if the method add is properly called, the arguments in properly called.

The problem is that the automocking is mocking everything. I would like to just mock the add function, as so I know what is going on.

I was able to find this solution, but I feel something is missing, when trying to transform theory in Jest.

//jest.mock("./functions"); // this happens automatically with automocking

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

test('calculate the average', () => {

    const spy = jest.spyOn(functions, 'add').mockImplementation((a, b) => console.log("I was called"));
    //functions.add.mockImplementation((a, b) => a + b);
    functions.average(2, 2)
    //expect(functions.average(2, 2)).toBe(2);
    expect(spy.mock.calls[0][1]).toBe(2);
})

Trying to implement a proposed solution, it makes sense, however, it does not work as I expected. I am unable to access the mock properties. The idea is that the information should be stored, as it does with conventional mocks, however, how can I access?

jest.mock('./functions', () => {
    // Require the original module to not be mocked...
    const originalModule = jest.requireActual('./functions');

    return {
        __esModule: true, // Use it when dealing with esModules
        ...originalModule,
        add: jest.fn((a, b) => a + b),
    };
});

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

test('calculate the average', () => {

    const add = require("./functions").add;

    functions.average(2, 2);
    expect(add.mock.calls[0][1]).toBe(2);

})

tried also:

test('calculate the average', () => {

    const add = require("./functions").add;
    expect(functions.average(2, 2)).toBe(2);
})

This showed me that the mock is "local", even after mocking, average is calling the normal method, in the case of spy, it calls the mocked one. It seems the only solution is spy aftermath.

P.S. I changed the mocked function in order to prove which function has been called.

2 Answers

Try something like this. This will only mock add function.

jest.mock("./functions", () => ({
    ...jest.requireActual("./functions"),
    add: jest.fn(() => {}) // Pass your mock implementation as param in jest.fn() 
}));

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

UPDATE

If you want to use actual function as well

describe("Mocking dependent functions", () => {
    afterEach(() => {
        jest.resetModules()
    });

    it('average should be 400', () => {
        const functions = jest.requireActual('./function');
        functions.add = jest.fn(() => 800); // using mock
        expect(functions.average(2, 2)).toBe(400);
    });
    
    it('average should be 2', () => {
        const functions = jest.requireActual('./function'); // using actual function
        expect(functions.average(2, 2)).toBe(2);
    });

    it('average should be 5', () => {
        const functions = jest.requireActual('./function');
        functions.add = jest.fn(() => 10); // using mock
        expect(functions.average(2, 2)).toBe(5);
    });
});

If you only want to use mock function

const functions = jest.requireActual('./function');
describe("Mocking dependent functions", () => {
    it('average should be 400', () => {
        functions.add = jest.fn(() => 800); // using mock
        expect(functions.average(2, 2)).toBe(400);
    });

    it('average should be 5', () => {
        functions.add = jest.fn(() => 10); // using mock
        expect(functions.average(2, 2)).toBe(5);
    });
})

In first approach, where you might want to use the actual functions as well, Jest was not able to clear the mocks i.e why I have to require the function file in every test case and after each test case I have to reset the module.

There is another way to solve this problem, but I don't prefer that. Still adding the code for your reference. Here you can keep a copy of original functions to refer it later

const functions = jest.requireActual('./function');
const originalFunctions = { ...functions };

describe("Mocking dependent functions", () => {
    it('average should be 400', () => {
        functions.add = jest.fn(() => 800); // using mock
        expect(functions.average(2, 2)).toBe(400);
    });
    
    it('average should be 2', () => {
        functions.add = originalFunctions.add; // getting actual function from copy
        expect(functions.average(2, 2)).toBe(2);
    });
});

Remember: When you call require(), you get an object with references to the module's functions. So when you overwrite the function, always try to restore the original state. Hence jest.resetModule() approach is the recommended one.

The test you are looking for ("just mock the add function") is this (and you can see it working here):

const functions = require('./functions');

jest.mock('./functions', () => ({
  ...jest.requireActual('./functions'),
  add: jest.fn((a, b) => a + b),
}));

test('calculate the average', () => 
    expect(functions.average(2, 2)).toBe(2)
);

As Sandeep highlighted, the key part of the test is jest.requireActual. jest.mock in this example is using the module factory parameter to return the mock module used for the test. Within the module factory, javascript object literal spread syntax is being used to create a new object i.e.

{
  ...jest.requireActual('./functions'),
  add: jest.fn((a, b) => a + b),
}

which has the actual methods (i.e. add and average), but the add function is being replaced with the mock version i.e. jest.fn((a, b) => a + b).

Related