Basado en esta pregunta ( ¿Cómo burlarse de los métodos de instancia de una clase burlada con jest.mock? ), ¿Cómo se puede burlar un método específico mientras se mantiene la implementación de todos los demás métodos?
Hay una pregunta similar ( Broma: cómo burlarse de un método específico de una clase ), pero esto solo se aplica si la instancia de la clase está disponible fuera de la clase que llama, por lo que esto no funcionaría si la instancia de la clase estuviera dentro de un constructor como en esta pregunta ( ¿Cómo simular una instancia de clase instanciada por un constructor usando broma? ).
Por ejemplo, se simula la clase Logger para que solo se method1 el método 1, pero luego falta el method2 , lo que genera un 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. }); }); Una solución es extender la clase Logger pero esto da como resultado un error undefined ya que el Logger ya está simulado:
// ... 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 Por lo tanto, ¿cuál podría ser la forma correcta de simular solo el method1 pero manteniendo la method2 original del método 2?
Puede usar jest.spyOn y proporcionar una implementación simulada para method1 .
// Logger.test.ts import Logger from './Logger'; jest.spyOn(Logger.prototype, "method1").mockImplementation(() => "mocked") describe("Logger", () => { it("calls method1 & method2 but only method1 is mocked", () => { const l = new Logger(); expect(l.method1()).toBe("mocked"); expect(l.method2()).toBe("method2"); }) })Pero en caso de que tenga muchos métodos y quiera simular cada uno de ellos excepto un único método, entonces puede obtener la implementación original de este único método utilizando jest.requireActual .
// Logger.test.ts import Logger from "./Logger"; const mockMethod1 = jest.fn().mockReturnValue("mocked"); const mockMethod3 = jest.fn().mockReturnValue("mocked"); const mockMethod4 = jest.fn().mockReturnValue("mocked"); const mockMethod5 = jest.fn().mockReturnValue("mocked"); jest.mock("./Logger", () => jest.fn().mockImplementation(() => ({ method1: mockMethod1, method2: jest.requireActual("./Logger").default.prototype.method2, method3: mockMethod3, method4: mockMethod4, method5: mockMethod5, })) ); describe("Logger", () => { it("calls all methods but only method1 is mocked", () => { const l = new Logger(); expect(l.method1()).toBe("mocked"); expect(l.method2()).toBe("method2"); expect(l.method3()).toBe("mocked"); expect(l.method4()).toBe("mocked"); expect(l.method5()).toBe("mocked"); }); });Nota: no necesita definir una clase ES6 para burlarse, una función de constructor también funciona bien porque las clases de ES6 son en realidad azúcar sintáctica para funciones de constructor.
Burlarse del prototipo funciona:
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'); }); });Sin embargo, no estoy seguro de si esta es la forma correcta de simular un método específico cuando no se puede acceder a la instancia de la clase, por lo que dejaré la pregunta abierta por si acaso hay mejores soluciones.