How to Mock Functions in the Same Module Using Jest

How to mock functions in the same module using Jest?

fwiw, the solution I settled on was to use dependency injection, by setting a default argument.

So I would change

export function bar () {
return 'bar';
}

export function foo () {
return `I am foo. bar is ${bar()}`;
}

to

export function bar () {
return 'bar';
}

export function foo (_bar = bar) {
return `I am foo. bar is ${_bar()}`;
}

This is not a breaking change to the API of my component, and I can easily override bar in my test by doing the following

import { foo, bar } from '../src/module';

describe('module', () => {
it('foo', () => {
const dummyBar = jest.fn().mockReturnValue('fake bar');
expect(foo(dummyBar)).toEqual('I am foo. bar is fake bar');
});
});

This has the benefit of leading to slightly nicer test code too :)

JavaScript tests - mocking function from same module with jest

I finally found the answer to this question. It's actually in the the Jest examples project on GitHub.

// Copyright 2004-present Facebook. All Rights Reserved.

/**
* This file illustrates how to do a partial mock where a subset
* of a module's exports have been mocked and the rest
* keep their actual implementation.
*/
import defaultExport, {apple, strawberry} from '../fruit';

jest.mock('../fruit', () => {
const originalModule = jest.requireActual('../fruit');
const mockedModule = jest.genMockFromModule('../fruit');

//Mock the default export and named export 'apple'.
return {
...mockedModule,
...originalModule,
apple: 'mocked apple',
default: jest.fn(() => 'mocked fruit'),
};
});

it('does a partial mock', () => {
const defaultExportResult = defaultExport();
expect(defaultExportResult).toBe('mocked fruit');
expect(defaultExport).toHaveBeenCalled();

expect(apple).toBe('mocked apple');
expect(strawberry()).toBe('strawberry');
});

mock one function using jest.mock() and not another in the same file

mock will not replace the original source-code file, but only add a new mocked version.

import { add } from './main';

jest.mock('./main', () => {
const actual = jest.requireActual('./main');
return {
// ...actual,
add: function () {
return 17;
}
};
});

describe('add', () => {
it('should use the mocked add function', () => {
expect(add(1, 2)).toBe(17);
});

it('should use the original add function', async () => {
const main = jest.requireActual('./main');
expect(main.add(1, 2)).toBe(3);
});
});

In your mock-factory function you replace add, but you use the original calculate function, which calls the original add function. This is why the mocked add function is not used.

To make it work the way you want, you also have to replace calculate (so that it can use the new add function), e.g.:

import { add, calculate, minus } from './main';

jest.mock('./main', () => ({
//__esModule: true,
...jest.requireActual('./main'),
calculate: function (x: number, y: number, operator: string) {
let result = -1;
switch (operator) {
case '+':
result = add(x, y);
break;
case '-':
result = minus(x, y);
break;
}
return result;
},
add: function () {
return 17;
}
}));

describe('calculate', () => {
it('should use the mocked calculate and add function', () => {
const result = calculate(3, 2, '+');
expect(result).toBe(17);
});
});

Since all these replacements are confusing and error prone, you should really use jest.spyOn instead.

Quote from this answer

Mutating an imported module is nasty and can lead to side effects like tests that pass or fail depending on execution order.

side note

If you really want to go this way, then you should at least move the operators and the calculate function to different files. Then you only need to mock the operators module.

operators.ts:

export function add(x: number, y: number): number {
return x + y;
}

export const minus = (x: number, y: number): number => {
return x - y;
};

calculate.ts:

import { add, minus } from './operators';

export const calculate = (x: number, y: number, operator: string) => {
let result = -1;
switch (operator) {
case '+':
result = add(x, y);
break;
case '-':
result = minus(x, y);
break;
}
return result;
};

calculate.spec.ts:

import { calculate } from './calculate';

jest.mock('./operators', () => ({
//__esModule: true,
...jest.requireActual('./operators'),
add: function () {
return 11;
}
}));

describe('calculate', () => {
it('should use the mocked add function', () => {
const result = calculate(3, 2, '+');
expect(result).toBe(11);
});
});

JavaScript unit testing: Mocking other function from same module

If otherFunc is exported by the module then simply using jest.fn() to mock it should work

const otherFuncMock = just.fn(x => module.otherFunc);
module.func();
expect(otherFuncMock).toHaveBeenCalledTimes(1);

If it isn't exported then I would suggest you don't need this test. The fact it calls another function is an internal implementation concern and you would be better focusing on testing the outcome of the combined function execution.

Jest mock module functions used in another module

I found 2 issues with the test

  1. When using jest.mock, object should be returned in the callback, in your case parenthesis is missing
jest.mock("./get-delivery-data", () => ({
getDeliveryDataFromApi: jest.fn(() => Promise.resolve(mockedDelieryData)),
}));

  1. Incorrect order of mocking imports.

First require imports the actual modules instead of mocked ones.

Working test

jest.mock("./get-order-data", () => ({
getOrderDataFromApi: jest.fn(() => Promise.resolve(mockedOrderData)),
}));
jest.mock("./get-delivery-data", () => ({
getDeliveryDataFromApi: jest.fn(() => Promise.resolve(mockedDelieryData)),
}));
const { combineOrderData } = require("./combine-order-data");

const mockedOrderData = {
id: 1,
amount: 1000,
};

const mockedDelieryData = {
orderId: 1,
status: "DELIVERED",
};

test("combineOrderData correctly combines data", async () => {
const combinedOrder = await combineOrderData(111);
});

Mock a function called by a tested function of the same file with jest

After lots of research, I found 2 ways to achieve this :

  • The first way is to call exports.a instead of a in the b function :

functions.js

export const a = (x) => { a very complicated function };

export const b = (x) => exports.a(x+1);

functions.test.js

import * as functions from './functions';

describe('b', () => {
test('calling b calls a with x+1', () => {
functions.a = jest.fn();
functions.b(1);
expect(functions.a).toHaveBeenCalledWith(2);
});
});

});
  • The second way is to change the prototype of b to accepts a function, defaulting to a:

functions.js

export const a = (x) => { a very complicated function };

export const b = (x, a = exports.a) => a(x + 1);

functions.test.js

import { a, b } from './functions';

describe('b', () => {
test('calling b calls a with x+1', () => {
const fakeA = jest.fn();
b(1, fakeA);
expect(fakeA).toHaveBeenCalledWith(2);
});
});


Related Topics



Leave a reply



Submit