Buns Test-Runner ist Jest-kompatibel und unterstützt die meisten Jest-Features. Tests werden in JavaScript oder TypeScript mit einer vertrauten API geschrieben.
Grundlegende Teststruktur
Ein einfacher Test verwendet die test-Funktion mit einer Callback-Funktion:
ts
import { test, expect } from "bun:test";
test("2 + 2 sollte 4 ergeben", () => {
expect(2 + 2).toBe(4);
});Test-Gruppierung mit describe
Verwenden Sie describe, um verwandte Tests zu gruppieren:
ts
import { describe, test, expect } from "bun:test";
describe("Mathematik-Operationen", () => {
test("Addition", () => {
expect(2 + 2).toBe(4);
});
test("Subtraktion", () => {
expect(5 - 3).toBe(2);
});
test("Multiplikation", () => {
expect(3 * 4).toBe(12);
});
});Assertions und Matchers
Gleichheit
ts
import { test, expect } from "bun:test";
test("Gleichheits-Matchers", () => {
// Exakte Gleichheit
expect(4).toBe(4);
expect("hello").toBe("hello");
// Tiefengleichheit für Objekte
expect({ a: 1 }).toEqual({ a: 1 });
expect([1, 2, 3]).toEqual([1, 2, 3]);
// Referenzgleichheit
const obj = { a: 1 };
expect(obj).toBe(obj);
expect({ a: 1 }).not.toBe({ a: 1 }); // Unterschiedliche Referenzen
});Wahrheit/Falschheit
ts
import { test, expect } from "bun:test";
test("Wahrheit/Falschheit", () => {
expect(true).toBeTruthy();
expect(false).toBeFalsy();
expect(null).toBeFalsy();
expect(undefined).toBeFalsy();
expect(0).toBeFalsy();
expect("").toBeFalsy();
expect("text").toBeTruthy();
expect(1).toBeTruthy();
});Vergleiche
ts
import { test, expect } from "bun:test";
test("Vergleichs-Matchers", () => {
expect(5).toBeGreaterThan(3);
expect(3).toBeLessThan(5);
expect(5).toBeGreaterThanOrEqual(5);
expect(3).toBeLessThanOrEqual(5);
});Null und undefiniert
ts
import { test, expect } from "bun:test";
test("Null und Undefiniert", () => {
expect(null).toBeNull();
expect(undefined).toBeUndefined();
expect(null).toBeDefined(); // null ist definiert
expect(undefined).not.toBeDefined();
});Zahlen
ts
import { test, expect } from "bun:test";
test("Zahlen-Matchers", () => {
expect(0.1 + 0.2).toBeCloseTo(0.3); // Floating-Point-Vergleich
expect(5).toBeNaN();
expect(NaN).toBeNaN();
expect(5).not.toBeNaN();
expect(5).toBeFinite();
expect(Infinity).not.toBeFinite();
});Strings
ts
import { test, expect } from "bun:test";
test("String-Matchers", () => {
expect("Hello World").toContain("World");
expect("Hello World").toMatch(/world/i);
expect("Hello World").toMatchObject("Hello World");
expect("Hello World").toHaveLength(11);
});Arrays
ts
import { test, expect } from "bun:test";
test("Array-Matchers", () => {
const arr = [1, 2, 3, 4, 5];
expect(arr).toContain(3);
expect(arr).toContainEqual(3);
expect(arr).toHaveLength(5);
expect(arr).toEqual(expect.arrayContaining([2, 3]));
expect(arr).not.toEqual(expect.arrayContaining([6]));
});Objekte
ts
import { test, expect } from "bun:test";
test("Objekt-Matchers", () => {
const user = {
id: 1,
name: "John",
email: "john@example.com",
};
expect(user).toHaveProperty("id");
expect(user).toHaveProperty("name", "John");
expect(user).toHaveProperty("email");
expect(user).not.toHaveProperty("password");
// Verschachtelte Eigenschaften
const nested = { user: { name: "John" } };
expect(nested).toHaveProperty("user.name", "John");
});Fehler
ts
import { test, expect } from "bun:test";
test("Fehler-Matchers", () => {
const throwError = () => {
throw new Error("Etwas ist schiefgelaufen");
};
expect(throwError).toThrow();
expect(throwError).toThrow(Error);
expect(throwError).toThrow("Etwas ist schiefgelaufen");
expect(throwError).toThrow(/schiefgelaufen/);
// Spezifische Fehlertypen
class CustomError extends Error {}
const throwCustomError = () => {
throw new CustomError("Benutzerdefinierter Fehler");
};
expect(throwCustomError).toThrow(CustomError);
});Asynchrone Tests
Async/Await
ts
import { test, expect } from "bun:test";
test("asynchrone Funktion mit async/await", async () => {
const result = await Promise.resolve("erfolgreich");
expect(result).toBe("erfolgreich");
});
test("asynchrone Funktion mit Fehler", async () => {
await expect(Promise.reject("Fehler")).rejects.toBe("Fehler");
});Promise-Matchers
ts
import { test, expect } from "bun:test";
test("Promise-Matchers", async () => {
// Aufgelöste Promises
await expect(Promise.resolve("wert")).resolves.toBe("wert");
await expect(Promise.resolve({ a: 1 })).resolves.toEqual({ a: 1 });
// Abgelehnte Promises
await expect(Promise.reject("fehler")).rejects.toBe("fehler");
await expect(Promise.reject(new Error("oops"))).rejects.toThrow(Error);
});Callbacks
ts
import { test, expect } from "bun:test";
test("Callback-Funktionen", (done) => {
const callback = (error: Error | null, result: string) => {
expect(error).toBeNull();
expect(result).toBe("erfolgreich");
done();
};
// Simuliere asynchrone Operation
setTimeout(() => {
callback(null, "erfolgreich");
}, 100);
});Test-Qualifikatoren
test.only
Führen Sie nur diesen Test aus:
ts
import { test, expect } from "bun:test";
test.only("nur dieser Test wird ausgeführt", () => {
expect(true).toBe(true);
});
test("dieser Test wird übersprungen", () => {
expect(true).toBe(true);
});test.skip
Überspringen Sie einen Test:
ts
import { test, expect } from "bun:test";
test.skip("übersprungener Test", () => {
expect(true).toBe(false); // Wird nicht ausgeführt
});
test("dieser Test wird ausgeführt", () => {
expect(true).toBe(true);
});test.todo
Markieren Sie einen Test als TODO:
ts
import { test, expect } from "bun:test";
test.todo("dieser Test muss noch implementiert werden");
test.todo("komplexer Test", () => {
// Implementierung kommt später
});test.failing
Markieren Sie einen Test, der erwartungsgemäß fehlschlägt:
ts
import { test, expect } from "bun:test";
test.failing("bekannter Fehler", () => {
expect(false).toBe(true); // Erwartet zu fehlschlagen
});
test.failing("noch nicht implementiert", () => {
throw new Error("Noch nicht implementiert");
});Parametrisierte Tests
Verwenden Sie test.each, um Tests mit verschiedenen Eingaben auszuführen:
ts
import { test, expect } from "bun:test";
test.each([
[1, 1, 2],
[2, 2, 4],
[3, 3, 6],
])("addiere %d + %d = %d", (a, b, expected) => {
expect(a + b).toBe(expected);
});
// Mit benannten Parametern
test.each`
a | b | expected
${1} | ${1} | ${2}
${2} | ${2} | ${4}
${3} | ${3} | ${6}
`("addiere $a + $b = $expected", ({ a, b, expected }) => {
expect(a + b).toBe(expected);
});Lifecycle-Hooks
ts
import { describe, test, expect, beforeAll, beforeEach, afterEach, afterAll } from "bun:test";
describe("Lifecycle-Beispiel", () => {
let counter: number;
beforeAll(() => {
console.log("Vor allen Tests");
});
afterAll(() => {
console.log("Nach allen Tests");
});
beforeEach(() => {
counter = 0;
console.log("Vor jedem Test");
});
afterEach(() => {
console.log("Nach jedem Test, counter:", counter);
});
test("Test 1", () => {
counter++;
expect(counter).toBe(1);
});
test("Test 2", () => {
counter += 2;
expect(counter).toBe(2);
});
});Mocking
Funktions-Mocks
ts
import { test, expect, mock } from "bun:test";
test("Funktions-Mock", () => {
const mockFn = mock((x: number) => x * 2);
expect(mockFn(5)).toBe(10);
expect(mockFn).toHaveBeenCalledTimes(1);
expect(mockFn).toHaveBeenCalledWith(5);
});spyOn
ts
import { test, expect, spyOn } from "bun:test";
test("Spy verwenden", () => {
const obj = {
method: () => "original",
};
const spy = spyOn(obj, "method").mockReturnValue("gemockt");
expect(obj.method()).toBe("gemockt");
expect(spy).toHaveBeenCalled();
});Modul-Mocks
ts
import { test, expect, mock } from "bun:test";
mock.module("./api", () => ({
fetchData: mock(async () => ({ data: "gemockt" })),
}));
test("Modul-Mock", async () => {
const { fetchData } = await import("./api");
const result = await fetchData();
expect(result.data).toBe("gemockt");
});Snapshot-Testing
ts
import { test, expect } from "bun:test";
test("Snapshot-Test", () => {
const data = {
id: 1,
name: "Test",
items: [1, 2, 3],
};
expect(data).toMatchSnapshot();
});
test("Inline-Snapshot", () => {
expect({ hello: "world" }).toMatchInlineSnapshot(`
{
"hello": "world",
}
`);
});Fortgeschrittene Muster
Test-Isolation
ts
import { test, expect, beforeEach, afterEach } from "bun:test";
let globalState: any;
beforeEach(() => {
// Sauberen Zustand für jeden Test sicherstellen
globalState = { counter: 0 };
jest.clearAllMocks();
});
afterEach(() => {
// Nach jedem Test bereinigen
globalState = null;
jest.restoreAllMocks();
});
test("Test mit isoliertem Zustand", () => {
globalState.counter++;
expect(globalState.counter).toBe(1);
});Geteilte Test-Hilfen
ts
import { test, expect } from "bun:test";
// Test-Hilfsfunktion
function createUser(name: string, age: number) {
return {
id: Math.random().toString(36).substr(2, 9),
name,
age,
createdAt: new Date(),
};
}
test("Benutzer erstellen", () => {
const user = createUser("John", 30);
expect(user.name).toBe("John");
expect(user.age).toBe(30);
expect(user.id).toBeDefined();
});Asynchrone Setup/Aufräumung
ts
import { test, expect, beforeAll, afterAll } from "bun:test";
let server: any;
let db: any;
beforeAll(async () => {
// Teure Ressourcen einmal einrichten
db = await connectToDatabase();
server = await startServer({ port: 3001 });
});
afterAll(async () => {
// Ressourcen nach allen Tests bereinigen
await server.stop();
await db.disconnect();
});
test("Server antwortet", async () => {
const response = await fetch("http://localhost:3001/health");
expect(response.status).toBe(200);
});Best Practices
Beschreibende Testnamen
ts
import { test, expect } from "bun:test";
// Gut: Klar und beschreibend
test("sollte Benutzer erfolgreich speichern, wenn gültige Daten bereitgestellt werden", () => {
// ...
});
// Vermeiden: Zu vage
test("Benutzer speichern", () => {
// ...
});Einzelne Verantwortung pro Test
ts
import { test, expect } from "bun:test";
// Gut: Jeder Test hat eine klare Verantwortung
test("sollte gültige E-Mail akzeptieren", () => {
expect(validateEmail("test@example.com")).toBe(true);
});
test("sollte ungültige E-Mail ablehnen", () => {
expect(validateEmail("invalid")).toBe(false);
});
// Vermeiden: Zu viele Assertions in einem Test
test("E-Mail-Validierung", () => {
expect(validateEmail("test@example.com")).toBe(true);
expect(validateEmail("invalid")).toBe(false);
expect(validateEmail("")).toBe(false);
expect(validateEmail("test@")).toBe(false);
// ... viele weitere Assertions
});Arrange-Act-Assert Muster
ts
import { test, expect } from "bun:test";
test("Benutzer aktualisieren", () => {
// Arrange
const user = { id: 1, name: "John" };
const updates = { name: "Jane" };
// Act
const updated = updateUser(user, updates);
// Assert
expect(updated.name).toBe("Jane");
expect(updated.id).toBe(1);
});Test-Daten isolieren
ts
import { test, expect, beforeEach } from "bun:test";
let testData: any;
beforeEach(() => {
// Frische Testdaten für jeden Test
testData = {
users: [{ id: 1, name: "Alice" }],
posts: [{ id: 1, title: "Hello" }],
};
});
test("Benutzer hinzufügen", () => {
testData.users.push({ id: 2, name: "Bob" });
expect(testData.users).toHaveLength(2);
});
test("Benutzer entfernen", () => {
testData.users = testData.users.filter(u => u.id !== 1);
expect(testData.users).toHaveLength(0);
});Tipps
Test-Abdeckung
bash
# Test-Abdeckung generieren
bun test --coverage
# Abdeckungsbericht anzeigen
bun test --coverage --coverage-reporter=htmlWatch-Mode für Entwicklung
bash
# Tests im Watch-Mode ausführen
bun test --watch
# Nur betroffene Tests bei Änderungen ausführen
bun test --watch --onlyChangedDebugging
ts
import { test, expect } from "bun:test";
test("Debug-Test", () => {
const result = someFunction();
console.log("Ergebnis:", result); // Debug-Ausgabe
expect(result).toBeDefined();
});bash
# Mit Debugger ausführen
bun test --inspect
bun test --inspect-brkPerformance-Optimierung
bash
# Gleichzeitige Testausführung
bun test --concurrent
# Timeout anpassen
bun test --timeout 10000
# Nur bestimmte Tests ausführen
bun test --testNamePattern="kritisch"