imagen

Mocking con Jest

subtractMocks en jest

  • Jest.fn Nos permite simular un método o función
  • jest.spyOn: Simula un método o función que le indiques y además permite restaurar la implementación original
  • Jest.mock: Simula un módulo entero

jest.fn

//math.ts
export function add(a: number, b: number) {
  return a + b;
}

export function subtract(a: number, b: number) {
  return a - b;
}

Nuestro SUT (subject under test, lo que estamos testeando)

//Calculator.ts
import * as arithmetic from './math';

export function doAdd(a: number, b: number) {
  return arithmetic.add(a, b);
}

export function doSubtract(a: number, b: number) {
  return arithmetic.subtract(a, b);
}

Ahora vamos al test

import * as arithmetic from "../../src/core/math";
import * as calculator from "../../src/core/Calculator";

describe('calculator', () => {
  // (1) Hacemos un MOCK del método add 
  (arithmetic as any).add = jest.fn();
  // (2) Hacemos un MOCK del método subtract, pero manteniendo la implementación inicial
  (arithmetic as any).subtract = jest.fn(math.subtract);

  it("calls add", () => {
    calculator.doAdd(1, 2);
    expect(arithmetic.add).toHaveBeenCalledWith(1, 2);
  });
  
  it("calls subtract", () => {
    calculator.doSubtract(1, 2);
    expect(arithmetic.subtract).toHaveBeenCalledWith(1, 2);
    expect(result).toBe(-1)
  });
});

jest.mock

Al usar jest.mock(<path>) crea un jest.fn() para cada uno de los métodos del modulo. Esto no siempre es bueno, por que nos dificulta el acceso a la implementación original.

import * as math from "../../src/core/math";
import * as calculator from "../../src/core/calculator";

// Creamos el mock de TODO el modulo
jest.mock("../../src/core/math");

describe('calculator', () => {
  it("calls add", () => {
    calculator.doAdd(1, 2);
    expect(arithmetic.add).toHaveBeenCalledWith(1, 2);
  });
  
  it("calls subtract", () => {
    calculator.doSubtract(1, 2);
    expect(arithmetic.subtract).toHaveBeenCalledWith(1, 2);
  });
});

jest.spyOn

Permite crear spy y stubs

describe('calculator', () => {
  it("calls add", () => {
    // Creamos el spy del método que nos interesa, y mantenemos la implementación original
    const mockedAdd = jest.spyOn(arithmetic, 'add');
    expect(calculator.doAdd(1, 2)).toBe(3);
    // Le preguntamos al Spy si el comportamiento fue el esperado
    expect(addMock).toHaveBeenCalledWith(1, 2);
  });
  it("calls subtract", () => {
    // Creamos el spy del método que nos interesa, y mantenemos la implementación original
    const mockedSubtract = jest.spyOn(arithmetic, 'subtract');
    // También podemo hacer un STUB del spy y "forzar" una respuesta, suplantando la implementación original
    mockedSubtract.mockImplementation(() => 10);
    expect(addMock).toHaveBeenCalledWith(1, 2);
  });
});

No abuses de los mocks, al definir un mock, le estamos diciendo al TEST como funciona el código que esta probando, y esto hará que nuestro test este "acoplado" al código que prueba y lo ideal es que el código que se esta probando (SUT) sea una caja negra (en lo posible).

Por ejemplo, en el caso de arriba, al suplantar subtract estamos acoplados a ese método, si este método cambia su comportamiento en el futuro quizás este test se rompa, de ahí el termino "test frágiles".