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
- 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)),
}));
- 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 ofa
in theb
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 toa
:
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
How to Pass Data from a Page to Another Page Using React Router
How to Set the Value Property in Angularjs' Ng-Options
Why Does Firebase Lose Reference Outside the Once() Function
Differencebetween Screenx/Y, Clientx/Y and Pagex/Y
When to Use Es6 Class Based React Components VS. Functional Es6 React Components
Concrete JavaScript Regular Expression for Accented Characters (Diacritics)
How to Get the Non-Enumerable Inherited Property Names of an Object
Using Jquery's Ajax Method to Retrieve Images as a Blob
JavaScript Document.Getelementsbyclassname Compatibility with Ie
JavaScript Filter Array of Objects
Display Posts in Descending Posted Order
Why Don't We Just Use Element Ids as Identifiers in JavaScript
How to Deal with Big Numbers in JavaScript
How to Get a Dom Element from a Jquery Selector
What's the Best Way to Convert a Number to a String in JavaScript
Difference Between 'Return Await Promise' and 'Return Promise'