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

Time:06-15

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.

  • Related