Was ist Jest?
Jest ist ein JavaScript Test-Runner, der bei Facebook entwickelt wurde und mittlerweile eines der am weitesten verbreiteten Testing-Tools im Ökosystem ist. Es ist „batteries-included“: Assertions, Mocking, Snapshot-Testing und Coverage sind alle in einem Paket enthalten, sodass ein neues Projekt nur sehr wenig Konfiguration benötigt, um startklar zu sein.
Jahrelang war Jest die Standardwahl für React- und Node-Projekte und ist in bestehenden Codebases nach wie vor dominant. Selbst angesichts des Aufstiegs von Vitest ist die API von Jest das gemeinsame Vokabular des JavaScript-Testings. Das macht es lohnenswert, Jest zu kennen, unabhängig davon, welchen Runner man im Alltag verwendet.
Tests schreiben
Eine Jest-Testdatei verwendet describe zur Gruppierung und test (oder it), um einen Testfall zu deklarieren.
// cart.test.js
import { Cart } from "./cart";
describe("Cart", () => {
test("starts empty", () => {
expect(new Cart().items).toEqual([]);
});
test("adds an item", () => {
const cart = new Cart();
cart.add({ id: 1, price: 10 });
expect(cart.items).toHaveLength(1);
});
});
Nutze beforeEach und afterEach für Setup und Teardown sowie beforeAll und afterAll, wenn eine Ressource einmalig für die gesamte Datei erstellt werden soll. Benenne Tests nach ihrem Verhalten, damit ein Fehler direkt verrät, was genau kaputtgegangen ist.
Matchers
Die expect API von Jest ist umfangreich und ausdrucksstark.
// matchers.test.js
expect(value).toBe(5); // strict equality
expect(value).toEqual({ a: 1 }); // deep equality
expect(value).toBeDefined();
expect(list).toContain("a");
expect(fn).toThrow("invalid");
expect(value).toBeGreaterThan(3);
expect(obj).toMatchObject({ id: 1 }); // partial match
toBe vergleicht Werte mit Object.is und eignet sich für Primitive. toEqual vergleicht Objekte und Arrays rekursiv. toStrictEqual ist strenger bei Typen und undefined Properties. toMatchObject ist nützlich, wenn Sie sich nur für eine Teilmenge einer Struktur interessieren.
Mocks, Spies und Module Mocking
Das Mocking in Jest ist eines seiner stärksten Features.
// users.test.js
import { jest } from "@jest/globals";
import { getUser } from "./users";
jest.mock("./http", () => ({
get: jest.fn().mockResolvedValue({ id: 1, name: "Ada" }),
}));
test("returns a user", async () => {
await expect(getUser(1)).resolves.toEqual({ id: 1, name: "Ada" });
});
jest.fn erstellt eine Mock-Funktion, jest.spyOn umschließt eine echte Methode, sodass Sie Aufrufe beobachten und diese später wiederherstellen können, und jest.mock ersetzt ein Modul. Da jest.mock über die Imports gehoben (hoisted) wird, ist der Mock bereits aktiv, bevor das zu testende Modul geladen wird.
Setzen Sie Mocks zwischen den Tests immer zurück. Aktivieren Sie clearMocks, resetMocks oder restoreMocks in der Konfiguration oder rufen Sie den entsprechenden ...AllMocks-Helper in afterEach auf. Ein überbleibender Mock-Status ist einer der häufigsten Gründe, warum Tests einzeln bestehen, aber gemeinsam fehlschlagen.
Asynchrones Testen
Verwenden Sie async/await zusammen mit resolves und rejects, genau so, wie Sie es in Vitest tun würden.
// async.test.js
test("rejects on failure", async () => {
await expect(loadUser(-1)).rejects.toThrow("Invalid id");
});
Der done-Callback funktioniert zwar, ist aber anfällig für Fehler. Bevorzugen Sie await. Für Timer machen jest.useFakeTimers() in Kombination mit jest.advanceTimersByTime() Tests für Debounce und Retry deterministisch.
Snapshots
Snapshots serialisieren einen Wert und vergleichen diesen bei zukünftigen Durchläufen.
// config.test.js
test("builds the default config", () => {
expect(createConfig({ debug: true })).toMatchInlineSnapshot();
});
Inline-Snapshots befinden sich direkt in der Testdatei, wodurch sie in einem Diff einfach überprüfbar sind. Externe Snapshots sind bei großen Ausgaben praktisch, werden aber häufig ohne genaue Prüfung aktualisiert, was sie zu einer bloßen Formsache macht. Nutzen Sie Snapshots für stabile, serialisierbare Daten und nicht als Ersatz dafür, sich Gedanken darüber zu machen, was wirklich wichtig ist.
Konfiguration
Jest liest die Konfiguration aus jest.config.js, einem jest-Key in package.json oder einem CLI-Flag.
// jest.config.js
export default {
testEnvironment: "jsdom",
clearMocks: true,
collectCoverage: true,
coverageThreshold: {
global: { lines: 80, functions: 80 },
},
transform: {
"^.+\\.(t|j)sx?$": ["@swc/jest"],
},
};
testEnvironment wählt zwischen node oder jsdom. clearMocks verhindert Leakage. Coverage-Thresholds machen aus einem Ziel eine verbindliche Regel. Der Transform legt fest, wie TypeScript und JSX kompiliert werden, wobei ts-jest, Babel und SWC die gängigen Optionen sind.
Wo Jest ins Spiel kommt
Jest bildet die Ebene für Unit- und Integrationstests. Um das Verhalten von Komponenten aus der Perspektive des Nutzers zu testen, kombiniere es mit Testing Library. Für vollständige User-Flows solltest du ein End-to-End-Tool wie Playwright oder Cypress verwenden. Wenn du ein neues Vite-Projekt startest, bietet Vitest dieselbe API mit einer schnelleren, gemeinsamen Pipeline.
Best Practices
- Testen Sie das Verhalten über öffentliche APIs, nicht über private Internals.
- Behalten Sie, sofern praktikabel, eine logische Assertion pro Test bei.
- Setzen Sie Mocks zwischen den Tests zurück und stellen Sie sie wieder her.
- Bevorzugen Sie
async/awaitgegenüberdone. - Verwenden Sie Fake Timer für alles, was zeitabhängig ist.
- Erstellen Sie Snapshots von kleinen, stabilen Strukturen und prüfen Sie jedes Update.
- Führen Sie Tests bei jeder Änderung in der CI aus, nicht nur lokal.
Häufige Fehler
- Assertions auf Implementierungsdetails, wodurch Tests bei Refactors fehlschlagen.
- Blindes Aktualisieren von Snapshots.
- Vergessen, Mocks zu leeren, was zu State-Leaks zwischen den Tests führt.
- Verwendung von echten Timern, was Tests langsam oder flaky macht.
- Alles auf Unit-Level testen und dadurch Integrationsfehler übersehen.
- Die Coverage ignorieren, während man fälschlicherweise annimmt, dass die Tests die wichtigen Pfade abdecken.
Wie geht es weiter?
Jest bietet ein zuverlässiges Fundament und eine gemeinsame Sprache innerhalb des gesamten Ökosystems. Vergleiche es mit Vitest für moderne Vite-Projekte, ergänze Testing Library für Komponenten und decke User Journeys mit Playwright oder Cypress ab. Schreibe anschließend einen Test für einen Bug, den du kürzlich behoben hast, damit dieser dich vor Regressionen schützt.