Home > other >  How to mock a specific method of a class whilst keeping the implementation of all other methods with
How to mock a specific method of a class whilst keeping the implementation of all other methods with

Time:05-18

Based on this question (How to mock instance methods of a class mocked with jest.mock?), how can a specific method be mocked whilst keeping the implementation of all other methods?

There's a similar question (Jest: How to mock one specific method of a class) but this only applies if the class instance is available outside it's calling class so this wouldn't work if the class instance was inside a constructor like in this question (How to mock a constructor instantiated class instance using jest?).

For example, the Logger class is mocked to have only method1 mocked but then method2 is missing, resulting in an error:

// Logger.ts
export default Logger() {
    constructor() {}
    method1() {
        return 'method1';
    }
    method2() {
        return 'method2';
    }
}

// Logger.test.ts
import Logger from './Logger';

jest.mock("./Logger", () => {
    return {
        default: class mockLogger {
            method1() {
                return 'mocked';
            }
        },
        __esModule: true,
    };
});

describe("Logger", () => {
    it("calls logger.method1() & logger.method2 on instantiation where only method1 is mocked", () => {
        const logger = new Logger(); // Assume this is called in the constructor of another object.

        expect(logger.method1()).toBe('mocked');
        expect(logger.method2()).toBe('method2'); // TypeError: logger.method2 is not a function.
    });
});

One solution is to extend the Logger class but this results in an undefined error as the Logger is already mocked:

// ...
jest.mock("./Logger", () => {
    return {
        default: class mockLogger extends Logger {
            override method1() {
                return 'mocked';
            }
        },
        __esModule: true,
    };
});
// ...
expect(logger.method2()).toBe('method2'); // TypeError: Cannot read property 'default' of undefined

Therefore, what could be the correct way to mock only method1 but keep method2's original implementation?

CodePudding user response:

Mocking the prototype works:

describe("Logger", () => {
    it("calls logger.method1() & logger.method2 on instantiation where only method1 is mocked", () => {
        Logger.prototype.method1 = jest.fn(() => 'mocked');
        const logger = new Logger();

        expect(logger.method1()).toBe('mocked');
        expect(logger.method2()).toBe('method2');
    });
});

However, I'm not sure if this is the correct way to mock a specific method when the class instance isn't accessible so I'll leave the question open for while in case there are better solutions.

CodePudding user response:

You can require the original module using jest.requireActual and then use the original implementation for method2 inside the module factory.

import Logger from "./Logger";

jest.mock("./Logger", () => {
  const { default: originalLogger } = jest.requireActual("./Animal.js");
  return {
    default: class mockLogger {
      method1() {
        return "mocked";
      }
      method2() {
        orginalLogger.prototype.module2();
      }
    },
    __esModule: true,
  };
});

describe("Logger", () => {
  it("calls logger.method1() & logger.method2 on instantiation where only method1 is mocked", () => {
    const logger = new Logger();
    expect(logger.method1()).toBe("mocked");
    expect(logger.method2()).toBe("method2");
  });
});
  • Related