Skip to content

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:

math.test.ts
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:

math.test.ts
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

equality.test.ts
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

truthy.test.ts
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

comparison.test.ts
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

null.test.ts
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

numbers.test.ts
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

strings.test.ts
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

arrays.test.ts
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

objects.test.ts
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

errors.test.ts
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

async.test.ts
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

promises.test.ts
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

callbacks.test.ts
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:

only.test.ts
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:

skip.test.ts
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:

todo.test.ts
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:

failing.test.ts
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:

each.test.ts
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

lifecycle.test.ts
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

mocks.test.ts
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

spy.test.ts
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

module-mock.test.ts
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

snapshot.test.ts
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

isolation.test.ts
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

helpers.test.ts
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

async-setup.test.ts
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

naming.test.ts
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

single-responsibility.test.ts
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

aaa-pattern.test.ts
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

test-data.test.ts
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=html

Watch-Mode für Entwicklung

bash
# Tests im Watch-Mode ausführen
bun test --watch

# Nur betroffene Tests bei Änderungen ausführen
bun test --watch --onlyChanged

Debugging

debug.test.ts
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-brk

Performance-Optimierung

bash
# Gleichzeitige Testausführung
bun test --concurrent

# Timeout anpassen
bun test --timeout 10000

# Nur bestimmte Tests ausführen
bun test --testNamePattern="kritisch"

Bun von www.bunjs.com.cn bearbeitet