I've the following code
main.ts
export const add = (x: number, y: number): number => {
return x y;
}
export const minus = (x: number, y: number): number => {
return x - y;
}
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;
}
I want to write the following test
main.test.ts
import {calculate} from "./main";
jest.mock("./main", () => ({
...jest.requireActual("./main"),
add: function () {
return 17;
}
}));
describe('testing', function () {
it('should test', function () {
const result = calculate(3, 2, ' ');
expect(result).toBe(5);
});
});
I want to do partial mocking, where I mock the add() function but I don't want to use the actual implementation for calculate(). But it doesn't work for me, add(3,2) just returns 5, instead of 17. I guess because they are both from the same module.
I know I can use jest.spyOn() but I wanted to understand why jest.mock() wouldn't work and how to fix it
CodePudding user response:
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);
});
});
CodePudding user response:
You can achieve this by using a wildcard alias import:
import * as calculation from "./main";
describe('testing', function () {
it('should test', function () {
jest.spyOn(calculation, 'add').mockImplementation(() => 17)
const result = calculation.calculate(3, 2, ' ');
expect(result).toBe(5);
});
});
Because there is no default import, you can simply change it to a wildcard (*
) alias (as
) and then explicitly spy on the function that you want to mock out and use mockImplementation
to mock out how it should behave. You can also move jest.spyOn(calculation, 'add').mockImplementation(() => 17)
above the describe
function for if you expect its behaviour to be consistent.