Skip to content

Il mocking e essenziale per i test permettendoti di sostituire le dipendenze con implementazioni controllate. Bun fornisce capacita di mocking complete incluse funzioni mock, spy e mock di modulo.

Funzioni Mock Base

Crea mock con la funzione 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);
});

Compatibilita con Jest

In alternativa, puoi usare la funzione jest.fn(), come in Jest. Si comporta in modo identico.

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);
});

Proprieta delle Funzioni Mock

Il risultato di mock() e una nuova funzione che e stata decorata con alcune proprieta aggiuntive.

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 }
//  ]

Proprieta e Metodi Disponibili

Le seguenti proprieta e metodi sono implementati sulle funzioni mock:

Proprieta/MetodoDescrizione
mockFn.getMockName()Restituisce il nome del mock
mockFn.mock.callsArray degli argomenti di chiamata per ogni invocazione
mockFn.mock.resultsArray dei valori di ritorno per ogni invocazione
mockFn.mock.instancesArray dei contesti this per ogni invocazione
mockFn.mock.contextsArray dei contesti this per ogni invocazione
mockFn.mock.lastCallArgomenti della chiamata piu recente
mockFn.mockClear()Cancella la cronologia delle chiamate
mockFn.mockReset()Cancella la cronologia delle chiamate e rimuove l'implementazione
mockFn.mockRestore()Ripristina l'implementazione originale
mockFn.mockImplementation(fn)Imposta una nuova implementazione
mockFn.mockImplementationOnce(fn)Imposta l'implementazione solo per la prossima chiamata
mockFn.mockName(name)Imposta il nome del mock
mockFn.mockReturnThis()Imposta il valore di ritorno a this
mockFn.mockReturnValue(value)Imposta un valore di ritorno
mockFn.mockReturnValueOnce(value)Imposta il valore di ritorno solo per la prossima chiamata
mockFn.mockResolvedValue(value)Imposta un valore Promise risolto
mockFn.mockResolvedValueOnce(value)Imposta il valore Promise risolto solo per la prossima chiamata
mockFn.mockRejectedValue(value)Imposta un valore Promise rifiutato
mockFn.mockRejectedValueOnce(value)Imposta il valore Promise rifiutato solo per la prossima chiamata
mockFn.withImplementation(fn, callback)Cambia temporaneamente l'implementazione

Esempi Pratici

Uso Base del Mock

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

test("comportamento della funzione mock", () => {
  const mockFn = mock((x: number) => x * 2);

  // Chiama il mock
  const result1 = mockFn(5);
  const result2 = mockFn(10);

  // Verifica le chiamate
  expect(mockFn).toHaveBeenCalledTimes(2);
  expect(mockFn).toHaveBeenCalledWith(5);
  expect(mockFn).toHaveBeenLastCalledWith(10);

  // Controlla i risultati
  expect(result1).toBe(10);
  expect(result2).toBe(20);

  // Ispeziona la cronologia delle chiamate
  expect(mockFn.mock.calls).toEqual([[5], [10]]);
  expect(mockFn.mock.results).toEqual([
    { type: "return", value: 10 },
    { type: "return", value: 20 },
  ]);
});

Implementazioni Mock Dinamiche

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

test("implementazioni mock dinamiche", () => {
  const mockFn = mock();

  // Imposta implementazioni diverse
  mockFn.mockImplementationOnce(() => "first");
  mockFn.mockImplementationOnce(() => "second");
  mockFn.mockImplementation(() => "default");

  expect(mockFn()).toBe("first");
  expect(mockFn()).toBe("second");
  expect(mockFn()).toBe("default");
  expect(mockFn()).toBe("default"); // Usa l'implementazione predefinita
});

Mock Asincroni

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

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

  // Mock dei valori risolti
  asyncMock.mockResolvedValueOnce("first result");
  asyncMock.mockResolvedValue("default result");

  expect(await asyncMock()).toBe("first result");
  expect(await asyncMock()).toBe("default result");

  // Mock dei valori rifiutati
  const rejectMock = mock();
  rejectMock.mockRejectedValue(new Error("Mock error"));

  await expect(rejectMock()).rejects.toThrow("Mock error");
});

Spy con spyOn()

E possibile tracciare le chiamate a una funzione senza sostituirla con un mock. Usa spyOn() per creare uno spy; questi spy possono essere passati a .toHaveBeenCalled() e .toHaveBeenCalledTimes().

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

const ringo = {
  name: "Ringo",
  sayHi() {
    console.log(`Hello I'm ${this.name}`);
  },
};

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

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

Uso Avanzato degli Spy

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

class UserService {
  async getUser(id: string) {
    // Implementazione originale
    return { id, name: `User ${id}` };
  }

  async saveUser(user: any) {
    // Implementazione originale
    return { ...user, saved: true };
  }
}

const userService = new UserService();

afterEach(() => {
  // Ripristina tutti gli spy dopo ogni test
  jest.restoreAllMocks();
});

test("spy sui metodi del servizio", async () => {
  // Spy senza cambiare l'implementazione
  const getUserSpy = spyOn(userService, "getUser");
  const saveUserSpy = spyOn(userService, "saveUser");

  // Usa il servizio normalmente
  const user = await userService.getUser("123");
  await userService.saveUser(user);

  // Verifica le chiamate
  expect(getUserSpy).toHaveBeenCalledWith("123");
  expect(saveUserSpy).toHaveBeenCalledWith(user);
});

test("spy con implementazione mock", async () => {
  // Spy e override dell'implementazione
  const getUserSpy = spyOn(userService, "getUser").mockResolvedValue({
    id: "123",
    name: "Mocked User",
  });

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

  expect(result.name).toBe("Mocked User");
  expect(getUserSpy).toHaveBeenCalledWith("123");
});

Mock di Modulo con mock.module()

Il mocking dei moduli ti permette di sovrascrivere il comportamento di un modulo. Usa mock.module(path: string, callback: () => Object) per mockare un modulo.

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");
});

Come il resto di Bun, i mock di modulo supportano sia import che require.

Sovrascrivere Moduli Gia Importati

Se hai bisogno di sovrascrivere un modulo che e gia stato importato, non c'e nulla di speciale che devi fare. Chiama semplicemente mock.module() e il modulo sara sovrascritto.

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

// Il modulo che andremo a mockare e qui:
import { foo } from "./module";

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

  // Lo aggiorniamo qui:
  mock.module("./module", () => {
    return {
      foo: "baz",
    };
  });

  // E i binding live vengono aggiornati.
  expect(foo).toBe("baz");

  // Il modulo e aggiornato anche per CJS.
  require.cjs.foo = "baz";
});

Hoisting e Preloading

Se hai bisogno di assicurarti che un modulo sia mockato prima di essere importato, dovresti usare --preload per caricare i tuoi mock prima che i test vengano eseguiti.

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

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

Per renderti la vita piu facile, puoi mettere il preload nel tuo bunfig.toml:

bunfig.toml
toml
[test]
# Carica questi moduli prima di eseguire i test.
preload = ["./my-preload"]

Best Practice per i Mock di Modulo

Quando Usare il Preload

Cosa succede se mocko un modulo che e gia stato importato?

Se mocki un modulo che e gia stato importato, il modulo sara aggiornato nella cache dei moduli. Questo significa che qualsiasi modulo che importa il modulo ricevera la versione mockata, MA il modulo originale sara stato comunque valutato. Questo significa che qualsiasi effetto collaterale dal modulo originale si sara comunque verificato.

Se vuoi impedire che il modulo originale venga valutato, dovresti usare --preload per caricare i tuoi mock prima che i test vengano eseguiti.

Esempi Pratici di Mock di Modulo

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

// Mock del modulo del client API
mock.module("./api-client", () => ({
  fetchUser: mock(async (id: string) => ({ id, name: `User ${id}` })),
  createUser: mock(async (user: any) => ({ ...user, id: "new-id" })),
  updateUser: mock(async (id: string, user: any) => ({ ...user, id })),
}));

test("servizio utente con API mockata", 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("User 123");
});

Mock di Dipendenze Esterne

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

// Mock della libreria database esterna
mock.module("pg", () => ({
  Client: mock(function () {
    return {
      connect: mock(async () => {}),
      query: mock(async (sql: string) => ({
        rows: [{ id: 1, name: "Test User" }],
      })),
      end: mock(async () => {}),
    };
  }),
}));

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

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

Funzioni Mock Globali

Cancella Tutti i Mock

Resetta lo stato di tutte le funzioni mock (chiamate, risultati, ecc.) senza ripristinare la loro implementazione originale:

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

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

test("cancellare tutti i mock", () => {
  random1();
  random2();

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

  mock.clearAllMocks();

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

  // Nota: le implementazioni sono preservate
  expect(typeof random1()).toBe("number");
  expect(typeof random2()).toBe("number");
});

Questo resetta le proprieta .mock.calls, .mock.instances, .mock.contexts e .mock.results di tutti i mock, ma a differenza di mock.restore(), non ripristina l'implementazione originale.

Ripristina Tutti i Mock

Invece di ripristinare manualmente ogni mock singolarmente con mockFn.mockRestore(), ripristina tutti i mock con un comando chiamando mock.restore(). Fare questo non resetta il valore dei moduli sovrascritti 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");

  // Valori originali
  expect(fooSpy).toBe("foo");
  expect(barSpy).toBe("bar");
  expect(bazSpy).toBe("baz");

  // Implementazioni mock
  fooSpy.mockImplementation(() => 42);
  barSpy.mockImplementation(() => 43);
  bazSpy.mockImplementation(() => 44);

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

  // Ripristina tutto
  mock.restore();

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

L'uso di mock.restore() puo ridurre la quantita di codice nei tuoi test aggiungendolo ai blocchi afterEach in ogni file di test o anche nel tuo codice di preload dei test.

Compatibilita con Vitest

Per una maggiore compatibilita con i test scritti per Vitest, Bun fornisce l'oggetto globale vi come alias per parti dell'API di mocking di Jest:

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

// Usare l'alias 'vi' simile a Vitest
test("compatibilita vitest", () => {
  const mockFn = vi.fn(() => 42);

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

  // Le seguenti funzioni sono disponibili sull'oggetto vi:
  // vi.fn
  // vi.spyOn
  // vi.mock
  // vi.restoreAllMocks
  // vi.clearAllMocks
});

Questo rende piu facile portare i test da Vitest a Bun senza dover riscrivere tutti i tuoi mock.

Dettagli di Implementazione

Capire come funziona mock.module() ti aiuta a usarlo piu efficacemente:

Interazione con la Cache

I mock di modulo interagiscono sia con le cache dei moduli ESM che CommonJS.

Valutazione Lazy

Il callback della factory del mock viene valutato solo quando il modulo viene effettivamente importato o richiesto.

Risoluzione dei Percorsi

Bun risolve automaticamente il specificatore del modulo come se stessi facendo un import, supportando:

  • Percorsi relativi ('./module')
  • Percorsi assoluti ('/path/to/module')
  • Nomi di pacchetti ('lodash')

Effetti del Timing dell'Import

  • Quando si mocka prima del primo import: Non si verificano effetti collaterali dal modulo originale
  • Quando si mocka dopo l'import: Gli effetti collaterali del modulo originale si sono gia verificati

Per questo motivo, l'uso di --preload e raccomandato per i mock che devono prevenire effetti collaterali.

Binding Live

I moduli ESM mockati mantengono i binding live, quindi cambiare il mock aggiornerà tutti gli import esistenti.

Pattern Avanzati

Funzioni Factory

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

function createMockUser(overrides = {}) {
  return {
    id: "mock-id",
    name: "Mock User",
    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 })),
};

Mock Condizionale

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: "mocked" })),
  }));
}

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

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

Pattern di Pulizia dei Mock

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

beforeEach(() => {
  // Imposta i mock comuni
  mock.module("./logger", () => ({
    log: mock(() => {}),
    error: mock(() => {}),
    warn: mock(() => {}),
  }));
});

afterEach(() => {
  // Pulizia di tutti i mock
  mock.restore();
  mock.clearAllMocks();
});

Best Practices

Mantieni i Mock Semplci

test.ts
ts
// Buono: Mock semplice e focalizzato
const mockUserApi = {
  getUser: mock(async id => ({ id, name: "Test User" })),
};

// Evita: Comportamento del mock troppo complesso
const complexMock = mock(input => {
  if (input.type === "A") {
    return processTypeA(input);
  } else if (input.type === "B") {
    return processTypeB(input);
  }
  // ... molta logica complessa
});

Usa Mock Type-Safe

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

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

Testa il Comportamento del Mock

test.ts
ts
test("il servizio chiama l'API correttamente", async () => {
  const mockApi = { fetchUser: mock(async () => ({ id: "1" })) };

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

  // Verifica che il mock sia stato chiamato correttamente
  expect(mockApi.fetchUser).toHaveBeenCalledWith("123");
  expect(mockApi.fetchUser).toHaveBeenCalledTimes(1);
});

Note

Auto-mocking

La directory __mocks__ e l'auto-mocking non sono ancora supportati. Se questo ti impedisce di passare a Bun, per favore segnala un issue.

ESM vs CommonJS

I mock di modulo hanno implementazioni diverse per i moduli ESM e CommonJS. Per i Moduli ES, Bun ha aggiunto patch a JavaScriptCore che permettono a Bun di sovrascrivere i valori degli export a runtime e aggiornare ricorsivamente i binding live.

Bun a cura di www.bunjs.com.cn