Skip to content

El mocking es esencial para pruebas permitiendo reemplazar dependencias con implementaciones controladas. Bun proporciona capacidades completas de mocking incluyendo mocks de funciones, spies y mocks de módulos.

Mocks de funciones básicas ​

Crea mocks con la función mock.

test.ts
ts
import { test, expect, mock } from "bun:test";

const random = mock(() => Math.random());

test("random", () => {
  const val = random();
  expect(val).toBeGreaterThan(0);
  expect(random).toHaveBeenCalled();
  expect(random).toHaveBeenCalledTimes(1);
});

Compatibilidad con Jest ​

Alternativamente, puedes usar la función jest.fn(), como en Jest. Se comporta idénticamente.

test.ts
ts
import { test, expect, jest } from "bun:test";

const random = jest.fn(() => Math.random());

test("random", () => {
  const val = random();
  expect(val).toBeGreaterThan(0);
  expect(random).toHaveBeenCalled();
  expect(random).toHaveBeenCalledTimes(1);
});

Propiedades de funciones mock ​

El resultado de mock() es una nueva función que ha sido decorada con algunas propiedades adicionales.

test.ts
ts
import { mock } from "bun:test";

const random = mock((multiplier: number) => multiplier * Math.random());

random(2);
random(10);

random.mock.calls;
// [[ 2 ], [ 10 ]]

random.mock.results;
//  [
//    { type: "return", value: 0.6533907460954099 },
//    { type: "return", value: 0.6452713933037312 }
//  ]

Propiedades y métodos disponibles ​

Las siguientes propiedades y métodos están implementados en funciones mock:

Propiedad/MétodoDescripción
mockFn.getMockName()Devuelve el nombre del mock
mockFn.mock.callsArray de argumentos de llamada para cada invocación
mockFn.mock.resultsArray de valores de retorno para cada invocación
mockFn.mock.instancesArray de contextos this para cada invocación
mockFn.mock.contextsArray de contextos this para cada invocación
mockFn.mock.lastCallArgumentos de la llamada más reciente
mockFn.mockClear()Limpia el historial de llamadas
mockFn.mockReset()Limpia el historial de llamadas y elimina la implementación
mockFn.mockRestore()Restaura la implementación original
mockFn.mockImplementation(fn)Establece una nueva implementación
mockFn.mockImplementationOnce(fn)Establece implementación solo para la siguiente llamada
mockFn.mockName(name)Establece el nombre del mock
mockFn.mockReturnThis()Establece el valor de retorno a this
mockFn.mockReturnValue(value)Establece un valor de retorno
mockFn.mockReturnValueOnce(value)Establece valor de retorno solo para la siguiente llamada
mockFn.mockResolvedValue(value)Establece un valor de Promesa resuelta
mockFn.mockResolvedValueOnce(value)Establece Promesa resuelta solo para la siguiente llamada
mockFn.mockRejectedValue(value)Establece un valor de Promesa rechazada
mockFn.mockRejectedValueOnce(value)Establece Promesa rechazada solo para la siguiente llamada
mockFn.withImplementation(fn, callback)Cambia temporalmente la implementación

Ejemplos prácticos ​

Uso básico de mock ​

test.ts
ts
import { test, expect, mock } from "bun:test";

test("comportamiento de función mock", () => {
  const mockFn = mock((x: number) => x * 2);

  // Llamar al mock
  const result1 = mockFn(5);
  const result2 = mockFn(10);

  // Verificar llamadas
  expect(mockFn).toHaveBeenCalledTimes(2);
  expect(mockFn).toHaveBeenCalledWith(5);
  expect(mockFn).toHaveBeenLastCalledWith(10);

  // Verificar resultados
  expect(result1).toBe(10);
  expect(result2).toBe(20);

  // Inspeccionar historial de llamadas
  expect(mockFn.mock.calls).toEqual([[5], [10]]);
  expect(mockFn.mock.results).toEqual([
    { type: "return", value: 10 },
    { type: "return", value: 20 },
  ]);
});

Implementaciones dinámicas de mock ​

test.ts
ts
import { test, expect, mock } from "bun:test";

test("implementaciones dinámicas de mock", () => {
  const mockFn = mock();

  // Establecer diferentes implementaciones
  mockFn.mockImplementationOnce(() => "primero");
  mockFn.mockImplementationOnce(() => "segundo");
  mockFn.mockImplementation(() => "por defecto");

  expect(mockFn()).toBe("primero");
  expect(mockFn()).toBe("segundo");
  expect(mockFn()).toBe("por defecto");
  expect(mockFn()).toBe("por defecto"); // Usa implementación por defecto
});

Mocks async ​

test.ts
ts
import { test, expect, mock } from "bun:test";

test("funciones mock async", async () => {
  const asyncMock = mock();

  // Mock de valores resueltos
  asyncMock.mockResolvedValueOnce("primer resultado");
  asyncMock.mockResolvedValue("resultado por defecto");

  expect(await asyncMock()).toBe("primer resultado");
  expect(await asyncMock()).toBe("resultado por defecto");

  // Mock de valores rechazados
  const rejectMock = mock();
  rejectMock.mockRejectedValue(new Error("Error de mock"));

  await expect(rejectMock()).rejects.toThrow("Error de mock");
});

Spies con spyOn() ​

Es posible rastrear llamadas a una función sin reemplazarla con un mock. Usa spyOn() para crear un spy; estos spies pueden pasarse a .toHaveBeenCalled() y .toHaveBeenCalledTimes().

test.ts
ts
import { test, expect, spyOn } from "bun:test";

const ringo = {
  name: "Ringo",
  sayHi() {
    console.log(`Hola soy ${this.name}`);
  },
};

const spy = spyOn(ringo, "sayHi");

test("spyon", () => {
  expect(spy).toHaveBeenCalledTimes(0);
  ringo.sayHi();
  expect(spy).toHaveBeenCalledTimes(1);
});

Uso avanzado de spy ​

test.ts
ts
import { test, expect, spyOn, afterEach } from "bun:test";

class UserService {
  async getUser(id: string) {
    // Implementación original
    return { id, name: `Usuario ${id}` };
  }

  async saveUser(user: any) {
    // Implementación original
    return { ...user, saved: true };
  }
}

const userService = new UserService();

afterEach(() => {
  // Restaurar todos los spies después de cada prueba
  jest.restoreAllMocks();
});

test("spy en métodos de servicio", async () => {
  // Spy sin cambiar implementación
  const getUserSpy = spyOn(userService, "getUser");
  const saveUserSpy = spyOn(userService, "saveUser");

  // Usar el servicio normalmente
  const user = await userService.getUser("123");
  await userService.saveUser(user);

  // Verificar llamadas
  expect(getUserSpy).toHaveBeenCalledWith("123");
  expect(saveUserSpy).toHaveBeenCalledWith(user);
});

test("spy con implementación mock", async () => {
  // Spy y anular implementación
  const getUserSpy = spyOn(userService, "getUser").mockResolvedValue({
    id: "123",
    name: "Usuario Mockeado",
  });

  const result = await userService.getUser("123");

  expect(result.name).toBe("Usuario Mockeado");
  expect(getUserSpy).toHaveBeenCalledWith("123");
});

Mocks de módulos con mock.module() ​

El mocking de módulos te permite anular el comportamiento de un módulo. Usa mock.module(path: string, callback: () => Object) para mockear un módulo.

test.ts
ts
import { test, expect, mock } from "bun:test";

mock.module("./module", () => {
  return {
    foo: "bar",
  };
});

test("mock.module", async () => {
  const esm = await import("./module");
  expect(esm.foo).toBe("bar");

  const cjs = require("./module");
  expect(cjs.foo).toBe("bar");
});

Como el resto de Bun, los mocks de módulos admiten tanto import como require.

Anular módulos ya importados ​

Si necesitas anular un módulo que ya ha sido importado, no hay nada especial que debas hacer. Solo llama a mock.module() y el módulo será anulado.

test.ts
ts
import { test, expect, mock } from "bun:test";

// El módulo que vamos a mockear está aquí:
import { foo } from "./module";

test("mock.module", async () => {
  const cjs = require("./module");
  expect(foo).toBe("bar");
  expect(cjs.foo).toBe("bar");

  // Lo actualizamos aquí:
  mock.module("./module", () => {
    return {
      foo: "baz",
    };
  });

  // Y los live bindings se actualizan.
  expect(foo).toBe("baz");

  // El módulo también se actualiza para CJS.
  expect(cjs.foo).toBe("baz");
});

Hoisting y precarga ​

Si necesitas asegurar que un módulo sea mockeado antes de ser importado, debes usar --preload para cargar tus mocks antes de que se ejecuten tus pruebas.

my-preload.ts
ts
import { mock } from "bun:test";

mock.module("./module", () => {
  return {
    foo: "bar",
  };
});
bash
bun test --preload ./my-preload

Para facilitarte la vida, puedes poner la precarga en tu bunfig.toml:

bunfig.toml
toml
[test]
# Cargar estos módulos antes de ejecutar pruebas.
preload = ["./my-preload"]

Mejores prácticas de mocks de módulos ​

Cuándo usar precarga ​

¿Qué pasa si mockeo un módulo que ya ha sido importado?

Si mockeas un módulo que ya ha sido importado, el módulo se actualizará en la caché de módulos. Esto significa que cualquier módulo que importe el módulo obtendrá la versión mockeada, PERO el módulo original todavía habrá sido evaluado. Eso significa que cualquier efecto secundario del módulo original todavía habrá ocurrido.

Si quieres prevenir que el módulo original sea evaluado, debes usar --preload para cargar tus mocks antes de que se ejecuten tus pruebas.

Ejemplos prácticos de mocks de módulos ​

api-client.test.ts
ts
import { test, expect, mock, beforeEach } from "bun:test";

// Mockear el módulo del cliente API
mock.module("./api-client", () => ({
  fetchUser: mock(async (id: string) => ({ id, name: `Usuario ${id}` })),
  createUser: mock(async (user: any) => ({ ...user, id: "new-id" })),
  updateUser: mock(async (id: string, user: any) => ({ ...user, id })),
}));

test("servicio de usuario con API mockeada", async () => {
  const { fetchUser } = await import("./api-client");
  const { UserService } = await import("./user-service");

  const userService = new UserService();
  const user = await userService.getUser("123");

  expect(fetchUser).toHaveBeenCalledWith("123");
  expect(user.name).toBe("Usuario 123");
});

Mockear dependencias externas ​

database.test.ts
ts
import { test, expect, mock } from "bun:test";

// Mockear biblioteca de base de datos externa
mock.module("pg", () => ({
  Client: mock(function () {
    return {
      connect: mock(async () => {}),
      query: mock(async (sql: string) => ({
        rows: [{ id: 1, name: "Usuario de prueba" }],
      })),
      end: mock(async () => {}),
    };
  }),
}));

test("operaciones de base de datos", async () => {
  const { Database } = await import("./database");
  const db = new Database();

  const users = await db.getUsers();
  expect(users).toHaveLength(1);
  expect(users[0].name).toBe("Usuario de prueba");
});

Funciones mock globales ​

Limpiar todos los mocks ​

Restablece el estado de todas las funciones mock (llamadas, resultados, etc.) sin restaurar su implementación original:

test.ts
ts
import { expect, mock, test } from "bun:test";

const random1 = mock(() => Math.random());
const random2 = mock(() => Math.random());

test("limpiando todos los mocks", () => {
  random1();
  random2();

  expect(random1).toHaveBeenCalledTimes(1);
  expect(random2).toHaveBeenCalledTimes(1);

  mock.clearAllMocks();

  expect(random1).toHaveBeenCalledTimes(0);
  expect(random2).toHaveBeenCalledTimes(0);

  // Nota: las implementaciones se preservan
  expect(typeof random1()).toBe("number");
  expect(typeof random2()).toBe("number");
});

Esto restablece las propiedades .mock.calls, .mock.instances, .mock.contexts y .mock.results de todos los mocks, pero a diferencia de mock.restore(), no restaura la implementación original.

Restaurar todos los mocks ​

En lugar de restaurar manualmente cada mock individualmente con mockFn.mockRestore(), restaura todos los mocks con un comando llamando a mock.restore(). Hacer esto no restablece el valor de los módulos anulados con mock.module().

test.ts
ts
import { expect, mock, spyOn, test } from "bun:test";

import * as fooModule from "./foo.ts";
import * as barModule from "./bar.ts";
import * as bazModule from "./baz.ts";

test("foo, bar, baz", () => {
  const fooSpy = spyOn(fooModule, "foo");
  const barSpy = spyOn(barModule, "bar");
  const bazSpy = spyOn(bazModule, "baz");

  // Valores originales
  expect(fooSpy).toBe("foo");
  expect(barSpy).toBe("bar");
  expect(bazSpy).toBe("baz");

  // Implementaciones mockeadas
  fooSpy.mockImplementation(() => 42);
  barSpy.mockImplementation(() => 43);
  bazSpy.mockImplementation(() => 44);

  expect(fooSpy()).toBe(42);
  expect(barSpy()).toBe(43);
  expect(bazSpy()).toBe(44);

  // Restaurar todos
  mock.restore();

  expect(fooSpy()).toBe("foo");
  expect(barSpy()).toBe("bar");
  expect(bazSpy()).toBe("baz");
});

Usar mock.restore() puede reducir la cantidad de código en tus pruebas agregándolo a bloques afterEach en cada archivo de prueba o incluso en tu código de precarga de pruebas.

Compatibilidad con Vitest ​

Para mayor compatibilidad con pruebas escritas para Vitest, Bun proporciona el objeto global vi como un alias para partes de la API de mocking de Jest:

test.ts
ts
import { test, expect } from "bun:test";

// Usando el alias 'vi' similar a Vitest
test("compatibilidad con vitest", () => {
  const mockFn = vi.fn(() => 42);

  mockFn();
  expect(mockFn).toHaveBeenCalled();

  // Las siguientes funciones están disponibles en el objeto vi:
  // vi.fn
  // vi.spyOn
  // vi.mock
  // vi.restoreAllMocks
  // vi.clearAllMocks
});

Esto facilita portar pruebas de Vitest a Bun sin tener que reescribir todos tus mocks.

Detalles de implementación ​

Entender cómo funciona mock.module() te ayuda a usarlo más efectivamente:

Interacción con caché ​

Los mocks de módulos interactúan con las cachés de módulos ESM y CommonJS.

Evaluación perezosa ​

La devolución de llamada de la fábrica de mocks solo se evalúa cuando el módulo es realmente importado o requerido.

Resolución de rutas ​

Bun resuelve automáticamente el especificador del módulo como si estuvieras haciendo un import, soportando:

  • Rutas relativas ('./module')
  • Rutas absolutas ('/path/to/module')
  • Nombres de paquetes ('lodash')

Efectos de tiempo de importación ​

  • Al mockear antes de la primera importación: No ocurren efectos secundarios del módulo original
  • Al mockear después de la importación: Los efectos secundarios del módulo original ya han ocurrido

Por esta razón, se recomienda usar --preload para mocks que necesitan prevenir efectos secundarios.

Live Bindings ​

Los módulos ESM mockeados mantienen live bindings, así que cambiar el mock actualizará todas las importaciones existentes.

Patrones avanzados ​

Funciones de fábrica ​

test.ts
ts
import { mock } from "bun:test";

function createMockUser(overrides = {}) {
  return {
    id: "mock-id",
    name: "Usuario Mock",
    email: "mock@example.com",
    ...overrides,
  };
}

const mockUserService = {
  getUser: mock(async (id: string) => createMockUser({ id })),
  createUser: mock(async (data: any) => createMockUser(data)),
  updateUser: mock(async (id: string, data: any) => createMockUser({ id, ...data })),
};

Mocking condicional ​

test.ts
ts
import { test, expect, mock } from "bun:test";

const shouldUseMockApi = process.env.NODE_ENV === "test";

if (shouldUseMockApi) {
  mock.module("./api", () => ({
    fetchData: mock(async () => ({ data: "mockeado" })),
  }));
}

test("uso condicional de API", async () => {
  const { fetchData } = await import("./api");
  const result = await fetchData();

  if (shouldUseMockApi) {
    expect(result.data).toBe("mockeado");
  }
});

Patrones de limpieza de mocks ​

test.ts
ts
import { afterEach, beforeEach } from "bun:test";

beforeEach(() => {
  // Configurar mocks comunes
  mock.module("./logger", () => ({
    log: mock(() => {}),
    error: mock(() => {}),
    warn: mock(() => {}),
  }));
});

afterEach(() => {
  // Limpiar todos los mocks
  mock.restore();
  mock.clearAllMocks();
});

Mejores prácticas ​

Mantener mocks simples ​

test.ts
ts
// Bueno: Mock simple y enfocado
const mockUserApi = {
  getUser: mock(async id => ({ id, name: "Usuario de prueba" })),
};

// Evitar: Comportamiento de mock overly complejo
const complexMock = mock(input => {
  if (input.type === "A") {
    return processTypeA(input);
  } else if (input.type === "B") {
    return processTypeB(input);
  }
  // ... mucha lógica compleja
});

Usar mocks con tipos seguros ​

ts
interface UserService {
  getUser(id: string): Promise<User>;
  createUser(data: CreateUserData): Promise<User>;
}

const mockUserService: UserService = {
  getUser: mock(async (id: string) => ({ id, name: "Usuario de prueba" })),
  createUser: mock(async data => ({ id: "new-id", ...data })),
};

Probar comportamiento de mocks ​

test.ts
ts
test("servicio llama a API correctamente", async () => {
  const mockApi = { fetchUser: mock(async () => ({ id: "1" })) };

  const service = new UserService(mockApi);
  await service.getUser("123");

  // Verificar que el mock fue llamado correctamente
  expect(mockApi.fetchUser).toHaveBeenCalledWith("123");
  expect(mockApi.fetchUser).toHaveBeenCalledTimes(1);
});

Notas ​

Auto-mocking ​

El directorio __mocks__ y el auto-mocking aún no están soportados. Si esto te impide cambiar a Bun, por favor abre un issue.

ESM vs CommonJS ​

Los mocks de módulos tienen diferentes implementaciones para módulos ESM y CommonJS. Para ES Modules, Bun ha agregado parches a JavaScriptCore que permiten a Bun anular valores de exportación en tiempo de ejecución y actualizar live bindings recursivamente.

Bun por www.bunjs.com.cn editar