Was ist Vitest?
Vitest ist ein Test-Runner, der auf Vite basiert. Er nutzt deine bestehende Vite-Konfiguration – inklusive Aliases, Plugins sowie TypeScript- und JSX-Transformationen –, sodass die Tests dieselbe Pipeline durchlaufen wie deine Anwendung. Dieses gemeinsame Fundament ist der Grund, warum Vitest schnell startet und schnell bleibt.
Die API wird dir bekannt vorkommen, wenn du bereits Jest verwendet hast. describe, it, expect und die Mocking-Helper funktionieren auf die gleiche Weise. Was sich ändert, ist die Engine im Hintergrund: Vitest führt Tests in parallelen Workern aus, überwacht Dateien mithilfe des Modul-Graphen von Vite und bietet erstklassigen TypeScript-Support ohne zusätzliche Konfiguration.
Deinen ersten Test schreiben
Eine Testdatei importiert aus vitest und beschreibt das Verhalten, das sie verifiziert.
// sum.test.ts
import { describe, it, expect } from "vitest";
import { sum } from "./sum";
describe("sum", () => {
it("adds two numbers", () => {
expect(sum(2, 3)).toBe(5);
});
it("handles negatives", () => {
expect(sum(-1, -1)).toBe(-2);
});
});
Benenne Tests nach dem Verhalten, nicht nach der Funktion. Ein fehlgeschlagener Test mit dem Namen „addiert zwei Zahlen“ sagt dir genau, was kaputtgegangen ist; einer namens „sum test 1“ hingegen nicht. Gruppiere zusammengehörige Testfälle mit describe und nutze beforeEach sowie afterEach für das Setup und Cleanup.
Assertions
Vitest liefert eine umfangreiche Matcher-API mit, die sehr nah an der von Jest liegt.
// assertions.test.ts
expect(value).toBe(5); // strict equality
expect(value).toEqual({ a: 1 }); // deep equality
expect(value).toBeTruthy();
expect(list).toHaveLength(3);
expect(list).toContain("a");
expect(fn).toThrow("invalid");
expect(value).toBeGreaterThan(3);
Verwenden Sie toEqual für Objekte und Arrays und toBe für Primitive und Referenzprüfungen. toMatchObject und toContainEqual sind nützlich, wenn Sie sich nur für einen Teil einer Struktur interessieren.
Mocks und Spies
Isolation ist zentral für Unit-Tests. Vitest bietet vi.fn für Mocks, vi.spyOn zum Wrappen bestehender Methoden und vi.mock zum Ersetzen ganzer Module.
// api.test.ts
import { vi, describe, it, expect } from "vitest";
import { fetchUser } from "./api";
vi.mock("./http", () => ({
get: vi.fn().mockResolvedValue({ id: 1, name: "Ada" }),
}));
describe("fetchUser", () => {
it("returns the user", async () => {
await expect(fetchUser(1)).resolves.toEqual({ id: 1, name: "Ada" });
});
});
vi.mock wird über die Imports gehoben (hoisted), sodass der Mock registriert wird, bevor das zu testende Modul ihn lädt. Wenn die Factory eine Variable benötigt, die in der Testdatei definiert ist, verwende vi.hoisted, damit der Wert existiert, bevor der Mock ausgeführt wird.
Asynchrones Testen
Warten Sie auf den Abschluss der Operation und prüfen Sie das Ergebnis. Vitest bietet die Helper resolves und rejects für Promises an.
// async.test.ts
it("rejects on failure", async () => {
await expect(loadUser(-1)).rejects.toThrow("Invalid id");
});
Bevorzugen Sie diesen Stil gegenüber dem done-Callback. Wenn done vergessen wird, führt dies zu Timeouts, die schwer zu diagnostizieren sind, und nicht behandelte Rejections können in andere Tests überlaufen. Für Fake Timer ermöglichen vi.useFakeTimers() und vi.advanceTimersByTime() das deterministische Testen von Debounces und Intervallen.
Snapshots
Snapshots halten einen Wert fest und vergleichen ihn bei späteren Durchläufen. Sie sind nützlich für große, stabile Ausgaben wie serialisierte Daten oder gerendertes Markup, lassen sich jedoch leicht missbrauchen.
// snapshot.test.ts
it("matches the shape", () => {
expect(createConfig({ debug: true })).toMatchSnapshot();
});
Ein Snapshot, der aktualisiert wird, ohne ihn zu prüfen, ist schlechter als gar kein Test. Nutzen Sie Snapshots für Datenstrukturen, bei denen ein vollständiger Vergleich unpraktisch ist, und bevorzugen Sie explizite Assertions, wenn Sie wissen, worauf es ankommt. toMatchInlineSnapshot hält den erwarteten Wert direkt in der Testdatei, was Code-Reviews sinnvoll macht.
Konfiguration
Vitest liest entweder einen test-Block aus deiner Vite-Konfiguration oder eine dedizierte vitest.config.ts aus.
// vitest.config.ts
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
environment: "jsdom",
globals: true,
coverage: {
provider: "v8",
reporter: ["text", "html"],
thresholds: { lines: 80 },
},
},
});
Mit der Option environment wählst du zwischen node, jsdom, happy-dom oder einem Browser. Für Komponententests sind jsdom oder happy-dom üblich; für reine Logik empfiehlt sich die schnelle node-Umgebung. Coverage-Thresholds verwandeln ein Coverage-Ziel in eine verbindliche Regel.
Vitest und die Testpyramide
Vitest bildet die Schicht für Unit- und Integrationstests. Kombiniere es mit einer Component-Testing-Library wie Testing Library für das UI-Verhalten und mit einem End-to-End-Tool wie Playwright für vollständige User-Flows. Zusammen decken sie die Pyramide ab: viele schnelle Unit-Tests, weniger Integrationstests und eine Handvoll End-to-End-Tests.
Best Practices
- Testen Sie das Verhalten, nicht die Implementierungsdetails.
- Konzentrieren Sie jeden Test auf genau ein Verhalten.
- Nutzen Sie
beforeEachfür das gemeinsame Setup und bereinigen Sie Mocks mitvi.clearAllMocks. - Bevorzugen Sie
resolvesundrejectsgegenüberdone-Callbacks. - Mocken Sie an den Grenzen (Netzwerk, Zeit, Zufall), nicht jeden internen Aufruf.
- Verwenden Sie Coverage-Thresholds, um stille Regressionen zu vermeiden.
- Führen Sie die Tests während der Entwicklung im Watch-Mode und in der CI bei jedem Push aus.
Häufige Fehler
- Assertions auf internen Zuständen oder privaten Methoden statt auf beobachtbarem Verhalten.
- Aktualisieren von Snapshots, ohne den Diff zu prüfen.
- Vergessen, Mocks zwischen den Tests zu leeren, was zu State-Leaks führt.
- Verwendung von echten Timern, wodurch Tests langsam und instabil (flaky) werden.
- Übermäßiges Mocking, bis der Test nur noch die Mocks selbst verifiziert.
- Testen desselben Verhaltens auf jeder Ebene der Testpyramide.
Wie geht es weiter?
Vitest ist der schnellste Weg, um mit dem Testen eines Vite-Projekts zu beginnen. Vergleiche es mit dem klassischen Jest, ergänze Testing Library für Komponenten sowie Playwright für End-to-End-Flows und nutze Vite, um die Konfiguration gemeinsam zu verwenden. Schreibe anschließend einige Tests für deinen bestehenden Code und erlebe, wie Refactorings sicher werden.