Skip to content

Definisci i test con un'API simile a Jest importata dal modulo integrato bun:test. A lungo termine, Bun mira alla compatibilita completa con Jest; al momento, e supportato un insieme limitato di matcher expect.

Uso Base

Per definire un semplice test:

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

test("2 + 2", () => {
  expect(2 + 2).toBe(4);
});

Raggruppare i Test

I test possono essere raggruppati in suite con describe.

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

describe("aritmetica", () => {
  test("2 + 2", () => {
    expect(2 + 2).toBe(4);
  });

  test("2 * 2", () => {
    expect(2 * 2).toBe(4);
  });
});

Test Asincroni

I test possono essere asincroni.

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

test("2 * 2", async () => {
  const result = await Promise.resolve(2 * 2);
  expect(result).toEqual(4);
});

In alternativa, usa il callback done per segnalare il completamento. Se includi il callback done come parametro nella definizione del test, devi chiamarlo o il test rimarra in attesa.

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

test("2 * 2", done => {
  Promise.resolve(2 * 2).then(result => {
    expect(result).toEqual(4);
    done();
  });
});

Timeout

Opzionalmente specifica un timeout per test in millisecondi passando un numero come terzo argomento a test.

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

test("wat", async () => {
  const data = await slowOperation();
  expect(data).toBe(42);
}, 500); // il test deve essere eseguito in meno di 500ms

In bun:test, i timeout dei test lanciano un'eccezione non catturabile per forzare il test a smettere di essere eseguito e fallire. Uccidiamo anche tutti i processi figli che sono stati spawnati nel test per evitare di lasciare dietro processi zombie in background.

Il timeout predefinito per ogni test e 5000ms (5 secondi) se non viene sovrascritto da questa opzione di timeout o jest.setDefaultTimeout().

Retry e Ripetizioni

test.retry

Usa l'opzione retry per ritentare automaticamente un test se fallisce. Il test passa se ha successo entro il numero di tentativi specificato. Questo e utile per test flaky che potrebbero fallire intermittenza.

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

test(
  "richiesta di rete flaky",
  async () => {
    const response = await fetch("https://example.com/api");
    expect(response.ok).toBe(true);
  },
  { retry: 3 }, // Ritenta fino a 3 volte se il test fallisce
);

test.repeats

Usa l'opzione repeats per eseguire un test piu volte indipendentemente dallo stato pass/fail. Il test fallisce se qualsiasi iterazione fallisce. Questo e utile per rilevare test flaky o per stress testing. Nota che repeats: N esegue il test N+1 volte in totale (1 esecuzione iniziale + N ripetizioni).

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

test(
  "assicurati che il test sia stabile", () => {
    expect(Math.random()).toBeLessThan(1);
  },
  { repeats: 20 }, // Esegue 21 volte in totale (1 iniziale + 20 ripetizioni)
);

NOTE

Non puoi usare sia `retry` che `repeats` sullo stesso test.

Processi Zombie Killer

Quando un test va in timeout e i processi spawnati nel test tramite Bun.spawn, Bun.spawnSync o node:child_process non vengono uccisi, saranno automaticamente uccisi e un messaggio sara registrato sulla console. Questo impedisce ai processi zombie di rimanere in background dopo test che hanno raggiunto il timeout.

Modificatori di Test

test.skip

Salta singoli test con test.skip. Questi test non saranno eseguiti.

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

test.skip("wat", () => {
  // TODO: fix this
  expect(0.1 + 0.2).toEqual(0.3);
});

test.todo

Marca un test come todo con test.todo. Questi test non saranno eseguiti.

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

test.todo("fix this", () => {
  myTestFunction();
});

Per eseguire i test todo e trovare qualcuno che sta passando, usa bun test --todo.

bash
bun test --todo
my.test.ts:
✗ unimplemented feature
  ^ this test is marked as todo but passes. Remove `.todo` or check that test is correct.

 0 pass
 1 fail
 1 expect() calls

Con questo flag, i test todo che falliscono non causeranno un errore, ma i test todo che passano saranno marcati come falliti in modo da poter rimuovere il marchio todo o correggere il test.

test.only

Per eseguire un particolare test o suite di test usa test.only() o describe.only().

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

test("test #1", () => {
  // non esegue
});

test.only("test #2", () => {
  // esegue
});

describe.only("only", () => {
  test("test #3", () => {
    // esegue
  });
});

Il seguente comando eseguira solo i test #2 e #3.

bash
bun test --only

Il seguente comando eseguira solo i test #1, #2 e #3.

bash
bun test

test.if

Per eseguire un test condizionalmente, usa test.if(). Il test eseguira se la condizione e truthy. Questo e particolarmente utile per test che dovrebbero essere eseguiti solo su architetture o sistemi operativi specifici.

example.test.ts
ts
test.if(Math.random() > 0.5)("runs half the time", () => {
  // ...
});

const macOS = process.platform === "darwin";
test.if(macOS)("runs on macOS", () => {
  // esegue se macOS
});

test.skipIf

Per saltare un test in base a qualche condizione, usa test.skipIf() o describe.skipIf().

example.test.ts
ts
const macOS = process.platform === "darwin";

test.skipIf(macOS)("runs on non-macOS", () => {
  // esegue se *non* macOS
});

test.todoIf

Se invece vuoi marcare il test come TODO, usa test.todoIf() o describe.todoIf(). Scegliere attentamente tra skipIf o todoIf puo mostrare una differenza tra, ad esempio, l'intento di "non valido per questo target" e "pianificato ma non ancora implementato".

example.test.ts
ts
const macOS = process.platform === "darwin";

// TODO: abbiamo implementato questo solo per Linux finora.
test.todoIf(macOS)("runs on posix", () => {
  // esegue se *non* macOS
});

test.failing

Usa test.failing() quando sai che un test sta attualmente fallendo ma vuoi tracciarlo ed essere notificato quando inizia a passare. Questo inverte il risultato del test:

  • Un test fallito marcato con .failing() passera
  • Un test passato marcato con .failing() fallira (con un messaggio che indica che ora sta passando e dovrebbe essere corretto)
ts
// Questo passara perche il test sta fallendo come previsto
test.failing("la matematica e rotta", () => {
  expect(0.1 + 0.2).toBe(0.3); // fallisce a causa della precisione in virgola mobile
});

// Questo fallira con un messaggio che il test ora sta passando
test.failing("bug corretto", () => {
  expect(1 + 1).toBe(2); // passa, ma ci aspettavamo che fallisse
});

Questo e utile per tracciare bug noti che pianifichi di correggere successivamente, o per implementare lo sviluppo guidato dai test.

Test Condizionali per Blocchi Describe

I modificatori condizionali .if(), .skipIf() e .todoIf() possono anche essere applicati ai blocchi describe, influenzando tutti i test all'interno della suite:

example.test.ts
ts
const isMacOS = process.platform === "darwin";

// Esegue l'intera suite solo su macOS
describe.if(isMacOS)("funzionalita specifiche di macOS", () => {
  test("feature A", () => {
    // esegue solo su macOS
  });

  test("feature B", () => {
    // esegue solo su macOS
  });
});

// Salta l'intera suite su Windows
describe.skipIf(process.platform === "win32")("funzionalita Unix", () => {
  test("feature C", () => {
    // saltato su Windows
  });
});

// Marca l'intera suite come TODO su Linux
describe.todoIf(process.platform === "linux")("Supporto Linux imminente", () => {
  test("feature D", () => {
    // marcato come TODO su Linux
  });
});

Test Parametrizzati

test.each e describe.each

Per eseguire lo stesso test con piu set di dati, usa test.each. Questo crea un test parametrizzato che viene eseguito una volta per ogni caso di test fornito.

math.test.ts
ts
const cases = [
  [1, 2, 3],
  [3, 4, 7],
];

test.each(cases)("%p + %p dovrebbe essere %p", (a, b, expected) => {
  expect(a + b).toBe(expected);
});

Puoi anche usare describe.each per creare una suite parametrizzata che viene eseguita una volta per ogni caso di test:

sum.test.ts
ts
describe.each([
  [1, 2, 3],
  [3, 4, 7],
])("add(%i, %i)", (a, b, expected) => {
  test(`ritorna ${expected}`, () => {
    expect(a + b).toBe(expected);
  });

  test(`la somma e maggiore di ogni valore`, () => {
    expect(a + b).toBeGreaterThan(a);
    expect(a + b).toBeGreaterThan(b);
  });
});

Passaggio degli Argomenti

Come gli argomenti vengono passati alla tua funzione di test dipende dalla struttura dei tuoi casi di test:

  • Se una riga della tabella e un array (come [1, 2, 3]), ogni elemento viene passato come argomento individuale
  • Se una riga non e un array (come un oggetto), viene passato come un singolo argomento
example.test.ts
ts
// Elementi dell'array passati come argomenti individuali
test.each([
  [1, 2, 3],
  [4, 5, 9],
])("add(%i, %i) = %i", (a, b, expected) => {
  expect(a + b).toBe(expected);
});

// Elementi oggetto passati come singolo argomento
test.each([
  { a: 1, b: 2, expected: 3 },
  { a: 4, b: 5, expected: 9 },
])("add($a, $b) = $expected", data => {
  expect(data.a + data.b).toBe(data.expected);
});

Specificatori di Formato

Ci sono diverse opzioni disponibili per formattare il titolo del test:

SpecificatoreDescrizione
%ppretty-format
%sString
%dNumber
%iInteger
%fFloating point
%jJSON
%oObject
%#Indice del caso di test
%%Singolo segno di percentuale (%)

Esempi

example.test.ts
ts
// Specificatori base
test.each([
  ["hello", 123],
  ["world", 456],
])("stringa: %s, numero: %i", (str, num) => {
  // "stringa: hello, numero: 123"
  // "stringa: world, numero: 456"
});

// %p per output pretty-format
test.each([
  [{ name: "Alice" }, { a: 1, b: 2 }],
  [{ name: "Bob" }, { x: 5, y: 10 }],
])("utente %p con dati %p", (user, data) => {
  // "utente { name: 'Alice' } con dati { a: 1, b: 2 }"
  // "utente { name: 'Bob' } con dati { x: 5, y: 10 }"
});

// %# per indice
test.each(["apple", "banana"])("frutta #%# e %s", fruit => {
  // "frutta #0 e apple"
  // "frutta #1 e banana"
});

Conteggio delle Asserzioni

Bun supporta la verifica che un numero specifico di asserzioni sia stato chiamato durante un test:

expect.hasAssertions()

Usa expect.hasAssertions() per verificare che almeno un'asserzione sia chiamata durante un test:

example.test.ts
ts
test("lavoro asincrono chiama asserzioni", async () => {
  expect.hasAssertions(); // Fallira se nessuna asserzione viene chiamata

  const data = await fetchData();
  expect(data).toBeDefined();
});

Questo e particolarmente utile per i test asincroni per assicurarti che le tue asserzioni siano effettivamente eseguite.

expect.assertions(count)

Usa expect.assertions(count) per verificare che un numero specifico di asserzioni sia chiamato durante un test:

example.test.ts
ts
test("esattamente due asserzioni", () => {
  expect.assertions(2); // Fallira se non esattamente 2 asserzioni sono chiamate

  expect(1 + 1).toBe(2);
  expect("hello").toContain("ell");
});

Questo aiuta a garantire che tutte le tue asserzioni siano eseguite, specialmente nel codice asincrono complesso con piu percorsi di codice.

## Test di Tipo

Bun include `expectTypeOf` per testare i tipi TypeScript, compatibile con Vitest.

### expectTypeOf

<Warning>
  Queste funzioni sono no-op a runtime - devi eseguire TypeScript separatamente per verificare i controlli di tipo.
</Warning>

La funzione `expectTypeOf` fornisce asserzioni a livello di tipo che sono controllate dal type checker di TypeScript. Per testare i tuoi tipi:

1. Scrivi le tue asserzioni di tipo usando `expectTypeOf`
2. Esegui `bunx tsc --noEmit` per verificare che i tuoi tipi siano corretti

```ts [example.test.ts]
import { expectTypeOf } from "bun:test";

// Asserzioni di tipo base
expectTypeOf<string>().toEqualTypeOf<string>();
expectTypeOf(123).toBeNumber();
expectTypeOf("hello").toBeString();

// Corrispondenza del tipo oggetto
expectTypeOf({ a: 1, b: "hello" }).toMatchObjectType<{ a: number }>();

// Tipi di funzione
function greet(name: string): string {
  return `Hello ${name}`;
}

expectTypeOf(greet).toBeFunction();
expectTypeOf(greet).parameters.toEqualTypeOf<[string]>();
expectTypeOf(greet).returns.toEqualTypeOf<string>();

// Tipi array
expectTypeOf([1, 2, 3]).items.toBeNumber();

// Tipi Promise
expectTypeOf(Promise.resolve(42)).resolves.toBeNumber();

Per la documentazione completa sui matcher di expectTypeOf, vedi il Riferimento API.

Matcher

Bun implementa i seguenti matcher. La piena compatibilita con Jest e in tabella di marcia; traccia i progressi qui.

Matcher Base

StatoMatcher
.not
.toBe()
.toEqual()
.toBeNull()
.toBeUndefined()
.toBeNaN()
.toBeDefined()
.toBeFalsy()
.toBeTruthy()
.toStrictEqual()

Matcher per Stringhe e Array

StatoMatcher
.toContain()
.toHaveLength()
.toMatch()
.toContainEqual()
.stringContaining()
.stringMatching()
.arrayContaining()

Matcher per Oggetti

StatoMatcher
.toHaveProperty()
.toMatchObject()
.toContainAllKeys()
.toContainValue()
.toContainValues()
.toContainAllValues()
.toContainAnyValues()
.objectContaining()

Matcher per Numeri

StatoMatcher
.toBeCloseTo()
.closeTo()
.toBeGreaterThan()
.toBeGreaterThanOrEqual()
.toBeLessThan()
.toBeLessThanOrEqual()

Matcher per Funzioni e Classi

StatoMatcher
.toThrow()
.toBeInstanceOf()

Matcher per Promise

StatoMatcher
.resolves()
.rejects()

Matcher per Funzioni Mock

StatoMatcher
.toHaveBeenCalled()
.toHaveBeenCalledTimes()
.toHaveBeenCalledWith()
.toHaveBeenLastCalledWith()
.toHaveBeenNthCalledWith()
.toHaveReturned()
.toHaveReturnedTimes()
.toHaveReturnedWith()
.toHaveLastReturnedWith()
.toHaveNthReturnedWith()

Matcher per Snapshot

StatoMatcher
.toMatchSnapshot()
.toMatchInlineSnapshot()
.toThrowErrorMatchingSnapshot()
.toThrowErrorMatchingInlineSnapshot()

Matcher di Utilita

StatoMatcher
.extend
.anything()
.any()
.assertions()
.hasAssertions()

Non Ancora Implementato

StatoMatcher
.addSnapshotSerializer()

Best Practices

Usa Nomi Descrittivi per i Test

example.test.ts
ts
// Buono
test("dovrebbe calcolare il prezzo totale incluse le tasse per piu articoli", () => {
  // implementazione del test
});

// Evita
test("calcolo prezzo", () => {
  // implementazione del test
});

Raggruppa Test Correlati

auth.test.ts
ts
describe("Autenticazione utente", () => {
  describe("con credenziali valide", () => {
    test("dovrebbe ritornare i dati utente", () => {
      // implementazione del test
    });

    test("dovrebbe impostare il token di autenticazione", () => {
      // implementazione del test
    });
  });

  describe("con credenziali non valide", () => {
    test("dovrebbe lanciare errore di autenticazione", () => {
      // implementazione del test
    });
  });
});

Usa Matcher Appropriati

auth.test.ts
ts
// Buono: usa matcher specifici
expect(users).toHaveLength(3);
expect(user.email).toContain("@");
expect(response.status).toBeGreaterThanOrEqual(200);

// Evita: usare toBe per tutto
expect(users.length === 3).toBe(true);
expect(user.email.includes("@")).toBe(true);
expect(response.status >= 200).toBe(true);

Testa le Condizioni di Errore

example.test.ts
ts
test("dovrebbe lanciare errore per input non valido", () => {
  expect(() => {
    validateEmail("not-an-email");
  }).toThrow("Formato email non valido");
});

test("dovrebbe gestire errori asincroni", async () => {
  await expect(async () => {
    await fetchUser("invalid-id");
  }).rejects.toThrow("Utente non trovato");
});

Usa Setup e Teardown

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

let testUser;

beforeEach(() => {
  testUser = createTestUser();
});

afterEach(() => {
  cleanupTestUser(testUser);
});

test("dovrebbe aggiornare il profilo utente", () => {
  // Usa testUser nel test
});

Bun a cura di www.bunjs.com.cn