Zum Hauptinhalt springen

JavaScript Unit Tests: Der umfassende Guide 2026

Meistere Unit-Tests in JavaScript mit unserem Guide für 2026. Behandelt Jest, Mocha, Setup, Mocking, CI und Tipps für Capacitor & Electron-Apps.

'Unit-Tests in JavaScript: Der umfassende Guide für 2026' Capgo Blog-Illustration

Du steckst wahrscheinlich gerade in einer von zwei Situationen: Entweder hat dein JavaScript-Projekt fast keine Tests und jeder Refactor fühlt sich riskant an, oder du hast schon Tests, aber die Hälfte davon ist langsam und brüchig, und man kann ihnen seltsam schwer vertrauen.

Das wird noch schlimmer in Capacitor und Electron Apps. Ein einfaches Feature kann im selben Flow gemeinsame Business-Logik, Browser-APIs, native Plugins, lokale Dateien, IPC und Remote-Services betreffen. Testest du diese Teile falsch, wird deine Suite zu einem Labyrinth aus Fake-Dependencies. Testest du sie richtig, bekommst du schnelles Feedback zu der Logik, die tatsächlich kaputtgeht.

Gute Unit-Tests in JavaScript beginnen nicht mit cleverer Matcher-Syntax. Sie beginnen mit einer disziplinierten Abgrenzung: Teste reine Logik direkt, isoliere Seiteneffekte und vermeide Tests, die sofort zusammenbrechen, wenn du eine interne Funktion umbenennst.

Inhaltsverzeichnis

Das richtige JavaScript-Testing-Framework wählen

Ein professionelles JavaScript-Projekt braucht einen echten Test Runner. Ad-hoc-Skripte und manuelle Console-Checks skalieren nicht, sobald mehrere Engineers an derselben Codebase arbeiten. Du brauchst Test Discovery, Assertions, Async-Handling, Mocks und eine Möglichkeit, alles konsistent lokal und in der CI auszuführen.

Aktuelle Empfehlungen konzentrieren sich auf eine kleine Auswahl an Mainstream-Optionen. Jest, Mocha und Jasmine werden immer wieder als die primären Frameworks hervorgehoben, wobei Jest oft für die integrierte Teststruktur, Assertions, Mocking und Async-Support in einem Paket hervorgehoben wird, wie in diesem Pluralsight JavaScript Testing Lab.

Eine Vergleichstabelle mit gängigen JavaScript-Testing-Frameworks wie Jest, Mocha, Cypress und Playwright.

Warum ein Framework unverzichtbar ist

Der häufigste Fehler von Teams ist es, Unit Tests als Nebensache zu behandeln. Das führt in der Regel zu inkonsistenter Dateibenennung, benutzerdefinierten Assertions, die niemand mehr kennt, und Helpers, die nur eine Person versteht.

Ein Framework gibt dir eine gemeinsame Sprache:

  • Test-Struktur mit describe und test oder it
  • Assertions mit lesbaren Matchern
  • Hooks für Setup und Teardown
  • Async-Unterstützung für Promises und Timer
  • Mocking-Tools für externe Abhängigkeiten

Wenn dein Team auch einen breiteren Überblick über Testautomatisierung jenseits der Unit-Level-Arbeit braucht, bietet Capgo einen nützlichen Überblick über automatisiertes Testen in App-Delivery-Workflows.

Jest vs. Mocha im Überblick

Jest und Mocha stehen für zwei unterschiedliche Philosophien.

Jest ist die All-in-One-Option. Sie bietet ab dem ersten Tag die meisten Funktionen, die Teams benötigen.
Mocha ist modularer. Es liefert einen Runner und erwartet, dass du den Rest des Stacks selbst zusammenstellst.

Funktion Jest Mocha
Setup-Komplexität Für die meisten Teams niedriger Höher, da du in der Regel Assertion- und Mocking-Bibliotheken hinzufügst
Assertions Integriert In der Regel mit einer anderen Bibliothek kombiniert
Mocking Integriert Meist in Kombination mit einer anderen Bibliothek
Async-Tests Integriert und unkompliziert Unterstützt, hängt aber stärker vom umgebenden Setup ab
Coverage-Workflow Meist in dieselbe Toolchain integriert Oft eher zusammengestückelt
Beste Wahl Neue Projekte, Teams, die Konsistenz suchen Legacy-Stacks, Teams, die modulare Kontrolle wollen

Praktische Regel: Wenn dein Team erst klären muss, welche Assertion- und Mocking-Libraries zum Runner passen, ist Jest wahrscheinlich die richtige Wahl.

Meine Empfehlung für die meisten Teams

Für die meisten modernen Projekte würde ich wählen: Jest es sei denn, der Code hat bereits starke Gründe, bei Mocha zu bleiben. Diese Empfehlung wird stärker, wenn die Anwendung Capacitor oder Electronenthält, da solche Projekte bereits genügend bewegliche Teile haben. Die Reduzierung der Test-Tool-Vielfalt zahlt sich schnell aus.

Mocha macht immer noch Sinn in älteren Node.js-Diensten oder langlebigen Codebasen, in denen das Ökosystem darum herum bereits etabliert ist. Aber für einen Mid-Level-Entwickler, der eine robuste Suite von Grund auf aufbaut, beseitigt Jest in der Regel mehr Reibung, als er erzeugt.

Ein wichtiger Hinweis zum Umfang. Cypress und Playwright sind hervorragende Tools, aber sie lösen ein anderes Problem. Sie eignen sich besser für Browser-Level- und End-to-End-Prüfungen, nicht für die schnelle innere Schleife, in der JavaScript-Unit-Tests ihren Platz haben sollten.

Projekt-Setup und dein erster Test

Ein sauberes Testing-Setup sollte langweilig sein. Wenn das Hinzufügen des ersten Tests kompliziert wirkt, bleibt die Suite wahrscheinlich nicht gesund.

Ein Mann mit Brille arbeitet an einem Holztisch an einem Programmierprojekt auf einem Laptop.

Ein einfaches Jest-Setup

Starte mit einem JavaScript-Projekt, das bereits ein package.jsonhat. Füge dann Jest als Dev-Dependency hinzu und richte ein Test-Skript ein.

{
  "scripts": {
    "test": "jest"
  }
}

Das reicht für viele Projekte. Du kannst später mehr Konfiguration hinzufügen, wenn dein Modulsystem, die Transpilation oder die Monorepo-Struktur es erfordert.

Wenn du lokal eine Capacitor-App entwickelst und deine Dev-Umgebung vor dem Hinzufügen von Tests für gemeinsame Logik in Ordnung bringen möchtest, ist Capgo’s Guide zu der Einrichtung einer lokalen Capacitor-Umgebung ein praktischer Begleiter.

Schreibe den Test vor dem code

Das Test-first-Muster ist keine persönliche Präferenz. Die JavaScript-Leitlinien des US-amerikanischen Consumer Financial Protection Bureau empfehlen ausdrücklich das Testen zuerst schreiben, Tests organisieren mit describe und it, und das Einrahmen von Prüfungen um expect(...) Assertions in seinem Leitfaden für JavaScript-Unit-Tests.

Das ist wichtig, weil Test-first verändert, wie du code designst. Funktionen werden kleiner, Abhängigkeiten sichtbarer, und Seiteneffekte schleichen sich nicht mehr in Logik ein, die rein bleiben sollte.

Hier ist ein minimales Beispiel:

// math.js
function addTax(amount, rate) {
  return amount + amount * rate;
}

module.exports = { addTax };
// math.test.js
const { addTax } = require('./math');

describe('addTax', () => {
  it('returns the amount with the tax applied', () => {
    expect(addTax(100, 0.2)).toBe(120);
  });
});

Nutze immer das Arrange-Act-Assert-Prinzip

Der Arrange, Act, Assert Das Muster hält Tests lesbar, auch wenn sie komplexer werden.

  1. Vorbereiten die Inputs und das benötigte Setup.
  2. Ausführen durch den Funktionsaufruf.
  3. Prüfen auf das Ergebnis.

Angewendet auf einen Validierungs-Helfer:

function isSupportedPlatform(platform) {
  return ['ios', 'android', 'web', 'desktop'].includes(platform);
}

describe('isSupportedPlatform', () => {
  it('returns true for ios', () => {
    // Arrange
    const platform = 'ios';

    // Act
    const result = isSupportedPlatform(platform);

    // Assert
    expect(result).toBe(true);
  });
});

Kleine Tests altern gut. Ein Test sollte in der Regel eine einzige Frage beantworten, nicht einen gesamten Workflow nacherzählen.

Für Capacitor- und Electron-Projekte ist diese Disziplin noch wichtiger, da eure reine Logik oft neben nativen oder Desktop-Integrationen code liegt. Haltet die Geschäftsregel testbar, ohne die Plattform-Runtime, und euer erster Test wird nicht euer letzter nützlicher sein.

Mocks und asynchrone Code meistern

Die meisten Bugs in Anwendungs-code entstehen nicht durch das Addieren zweier Zahlen. Sie entstehen durch code, die über sich hinauswachsen: Netzwerk-Anfragen, Dateien, Plugin-APIs, Timer, IPC-Kanäle, Storage-Layer.

Genau hier hilft Mocking. Es gibt euch Kontrolle über die Grenze, damit der Test sich auf die Entscheidungslogik eures code konzentrieren kann.

Ein Whiteboard-Diagramm, das eine Microservices-Architektur mit APIs, Datenbanken, externen Diensten und ereignisgesteuertem Datenfluss zeigt.

Nur Grenzen mocken, nicht alles

Leitfäden für wartbare Tests betonen Abdeckung einzelner Verhaltensweisen und eine starke Assertion pro Test, und es warnt außerdem davor, dass zu viele Mocks Tests brüchig machen und sie eng an Implementierungsdetails koppeln, wie in diesem TestRail-Artikel über wartbare Unit-Tests zusammengefasst wird.

Diese Warnung ist in JavaScript extrem wichtig. Teams fangen oft damit an, jedes importierte Modul zu mocken, und testen am Ende nur noch, ob Funktionen andere Funktionen in der „richtigen“ Reihenfolge aufrufen, statt das tatsächliche Verhalten zu prüfen.

Schlechtes Ziel für einen Mock-lastigen Test:

  • ob Helper A Helper B aufgerufen hat
  • ob Service C Serializer D aufgerufen hat
  • ob eine interne private Funktion zweimal ausgeführt wurde

Besseres Ziel:

  • was die Funktion zurückgegeben hat
  • ob sie eine fehlgeschlagene Dependency korrekt behandelt hat
  • ob sie Daten in die erwartete Struktur transformiert hat

Ein besseres Pattern für Capacitor und Electron code

Bei Mobile- und Desktop-Apps bevorzuge ich eine Wrapper-Schicht um native oder Plattform-APIs. So mocken Unit Tests den Wrapper und nicht die Plattform selbst.

Beispielstruktur:

// cameraGateway.js
async function getPhoto(cameraPlugin) {
  return cameraPlugin.getPhoto();
}

module.exports = { getPhoto };
// profilePhotoService.js
async function loadProfilePhoto(cameraGateway) {
  const photo = await cameraGateway.getPhoto();
  return { path: photo.path, ready: true };
}

module.exports = { loadProfilePhoto };
// profilePhotoService.test.js
const { loadProfilePhoto } = require('./profilePhotoService');

test('returns mapped photo data', async () => {
  const fakeCameraGateway = {
    getPhoto: jest.fn().mockResolvedValue({ path: '/tmp/pic.jpg' })
  };

  const result = await loadProfilePhoto(fakeCameraGateway);

  expect(result).toEqual({ path: '/tmp/pic.jpg', ready: true });
});

Dieses Pattern funktioniert auch für Electron. Wickle ipcRenderer, Dateizugriffe oder Shell-Integrationen in einen dünnen Adapter ein. Unit Tests greifen auf die Service-Schicht zu, nicht direkt auf die Runtime.

Für Teams, die Release-Logik und Update-Pfade in Capacitor Apps testen, bietet Capgo einen hilfreichen Guide zum Testen von Capacitor OTA-Updates mit Mock-Szenarien.

Ein kurzer Walkthrough hilft, wenn dein Team den Async-Teststil noch vereinheitlicht:

Async-Flows testen ohne Flakiness

Nutzt async/await in Tests, wenn der zu testende code ein Promise zurückgibt. Das ist klarer als callback-lastige Patterns und einfacher zu debuggen.

async function fetchProfile(api) {
  const response = await api.getUser();
  return response.name;
}

test('returns the user name from the API response', async () => {
  const api = {
    getUser: jest.fn().mockResolvedValue({ name: 'Ava' })
  };

  const result = await fetchProfile(api);

  expect(result).toBe('Ava');
});

Teste auch den Failure-Pfad:

test('throws when the API request fails', async () => {
  const api = {
    getUser: jest.fn().mockRejectedValue(new Error('network failed'))
  };

  await expect(fetchProfile(api)).rejects.toThrow('network failed');
});

Teste sowohl den Happy Path als auch den Ugly Path. In der Produktion ist der Ugly Path meist der, an den sich Nutzer erinnern.

Fortgeschrittene Strategien für robuste Tests

Eine Testsuite ist erst dann nützlich, wenn sie auch nach den Änderungen an code noch Bestand hat. Das ist schwieriger, als einfach nur einen Haufen grüner Tests zu schreiben.

Ein Diagramm, das Strategien für robuste Software durch umfassende Testabdeckung und wartbare Testsuiten veranschaulicht.

Nutze den Test-Split als Budget

Ein praktischer Leitfaden empfiehlt eine 70/20/10 Aufteilung auf Unit-, Integration- und End-to-End-Tests, wobei Unit Tests das schnellste Feedback und die stabilsten Fehler liefern. Laut derselben Anleitung sollte eine vollständige Unit-Test-Suite idealerweise in unter 10 Sekunden fertig sein sollte, und Pre-Commit-Checks sollten unter 5 Sekunden bleiben, according to this OpenReplay-Testing-Guide.

Ich sehe das eher als Budget-Tool denn als Religion. Wenn der Großteil eurer Arbeit in E2E-Tests fließt, wartet euer Team zu lange auf Feedback. Wenn ihr nur Unit-Tests macht, überseht ihr echte Systemgrenzen.

Für eine Capacitor oder Electron-App sieht eine gesunde Balance meistens so aus:

  • Unit-Tests für Preislogik, Berechtigungsregeln, Serialisierung, Update-Berechtigung, Feature Flags und State Transforms
  • Integrationstests für Storage-Adapter, Plugin-Wrapper und IPC-Contracts
  • E2E-Tests für einige kritische User Journeys wie Login, Kaufprozess, Sync oder Update-Prompts

Coverage ist eine Taschenlampe, kein Ziel

Coverage-Reports sind nützlich, wenn sie helfen, ungetestete Branches in wichtiger Logik zu finden. Sie werden jedoch zum Problem, wenn Teams Coverage-Prozentzahlen nur um der Zahl willen jagen.

Ein Login-Validator mit durchdachten Edge-Case-Tests liefert mehr Wert als eine abgedeckte Datei voller trivialer Assertions. Das gilt besonders für input-lastige code wie Formulare, Parser, Datumslogik und Berechtigungsprüfungen. Wenn dein Team die Qualität bei validierungsintensiver UI schärft, ist dieser Guide zu Frontend-Formularvalidierung eine gute Ergänzung zur Unit-Test-Strategie.

Behavior-first-Tests überstehen Refactors

Eine zuverlässige Test-Suite sollte es dir ermöglichen, Interna zu refaktorisieren, ohne die Hälfte der Tests neu schreiben zu müssen. Der einfachste Weg dorthin ist, das beobachtbare Verhalten statt Implementierungsdetails zu asserten.

Gut skalierende Use Cases:

  • Boundary conditions wie leere Eingaben, null-ähnliche Werte, ungültige Typen und überlange Strings
  • Domänenergebnisse z. B. „verweigert bei fehlender Berechtigung“
  • Zustandsübergänge z. B. „markiert Update als pending, nachdem Download-Metadaten validiert wurden“

Use Cases, die schnell veralten:

  • Prüfen interner Helper-Aufrufe
  • Asserts auf die Sequenz privater Methoden
  • Mocken jeder Ebene in der Call Chain

Für App-Teams, die disziplinierte Release-Prozesse aufbauen, ist der Artikel von Capgo zu App-Qualitätssicherung ist nützlich, weil es die Testarbeit mit dem übergeordneten Release-Pipeline verbindet.

Testing für CI, Capacitor und Electron Apps

Ein Test, der nur auf dem Rechner eines Entwicklers läuft, ist kein Sicherheitsnetz. Es ist ein lokales Gewohnheit.

CI macht JavaScript-Unit-Tests zu Team-Infrastruktur. Jeder Push, Pull Request oder Release-Branch kann dieselben Befehle mit denselben Erwartungen ausführen. Diese Konsistenz ist besonders wichtig für Capacitor- und Electron-Projekte, bei denen Umgebungsabweichungen zu subtilen Fehlern führen.

CI zum Standard-Ausführungspfad machen

Mindestens sollte euer CI die Abhängigkeiten installieren und die Unit-Test-Suite bei jeder Änderung ausführen. Haltet den Befehl, wenn möglich, identisch zur lokalen Entwicklung.

Ein grundlegendes GitHub-Actions-Workflow kann so klein sein wie dieses:

name: test

on: [push, pull_request]

jobs:
  unit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npm test

Das reicht aus, um kaputte Imports, fehlgeschlagene Assertions und versehentliche Plattformannahmen zu erkennen, bevor sie in main landen.

Für Mobile-Teams, die über automatisierte Pipelines ausliefern, hat Capgo einen praktischen Leitfaden zu das Setup von CI/CD für Capacitor-Apps.

Interaktionen mit Capacitor-Plugins testen

Der falsche Weg, Capacitor code zu unit testen, ist, native Plugins direkt in jeden Service zu ziehen. Das koppelt deine Test-Suite an die Plattform-Bridge.

Das bessere Muster ist eine dünne Abstraktion:

// deviceStorage.js
async function saveFile(filesystem, path, data) {
  return filesystem.writeFile({ path, data });
}

module.exports = { saveFile };
// draftService.js
async function persistDraft(storage, draft) {
  await storage.save('draft.json', JSON.stringify(draft));
  return { saved: true };
}

module.exports = { persistDraft };
// draftService.test.js
const { persistDraft } = require('./draftService');

test('persists a serialized draft', async () => {
  const storage = {
    save: jest.fn().mockResolvedValue(undefined)
  };

  const result = await persistDraft(storage, { title: 'Hello' });

  expect(result).toEqual({ saved: true });
});

Dasselbe gilt für Kamera-Zugriff, biometrische Prompts, Push-Token-Registrierung und Netzwerkstatus. Halte Plugin-Aufrufe in Adaptern. Teste die App-Logik gegen Interfaces, die du kontrollierst.

Electron Main Renderer und IPC code testen

Electron-Apps haben zwei wichtige Nahtstellen: Main-Prozess code und Renderer-Prozess code. Verwische sie nicht in Tests.

Ein zuverlässiges Setup trennt normalerweise:

  • Renderer-Unit-Tests für View Models, State, Formatierung und UI-seitige Geschäftslogik
  • Unit-Tests für den Main-Prozess für Menüs, Dateioperationen und App-Lifecycle-Entscheidungen
  • IPC-Vertrags-Tests für Nachrichtenformat und erwartete Antworten

Beispiel für einen IPC-Wrapper:

// ipcGateway.js
function sendSettings(ipcRenderer, payload) {
  ipcRenderer.send('settings:update', payload);
}

module.exports = { sendSettings };
// ipcGateway.test.js
const { sendSettings } = require('./ipcGateway');

test('sends settings update over ipc', () => {
  const ipcRenderer = { send: jest.fn() };

  sendSettings(ipcRenderer, { theme: 'dark' });

  expect(ipcRenderer.send).toHaveBeenCalledWith('settings:update', { theme: 'dark' });
});

Wenn du später die interne Implementierung von einem Helper auf einen anderen änderst, bleibt dieser Test gültig, weil er das Verhalten prüft, auf das es ankommt. Das ist der Standard, den du für Desktop und Mobile code anstrebst.

Häufig gestellte Fragen zu JavaScript Unit-Tests

Was ist der Unterschied zwischen Unit-, Integration- und E2E-Tests?

Ein Unit-Test prüft ein kleines Stück Logik isoliert. Ein Integration-Test prüft, ob einige Komponenten oder Services korrekt zusammenarbeiten. Ein End-to-End-Test durchläuft einen User-Journey in der laufenden Anwendung.

Nutze Unit-Tests für schnelle Sicherheit bei Business Rules. Nutze Integration-Tests für Schnittstellen wie Storage, Plugin-Wrapper und IPC. Nutze E2E-Tests sparsam für Workflows, die bei einem Ausfall richtig wehtun würden.

Sollten wir eine vollständige Coverage anstreben?

Nein. Vollständige Abdeckung kann Teams zu Tests mit geringem Mehrwert verleiten.

Abdeckung ist nützlich, wenn sie riskanten code aufdeckt, den niemand getestet hat. Sie ist nutzlos, wenn Entwickler oberflächliche Assertions nur hinzufügen, um ein Dashboard zu befriedigen. Wenn deine Suite fragil ist, rettet mehr Abdeckung sie nicht.

Wie wir Tests in eine bestehende Codebase integrieren

Beginne dort, wo Änderungen bereits stattfinden. Freeze das Team nicht und künde keinen massiven Rewrite der Teststrategie an.

Eine praktische Abfolge sieht so aus:

  • Schütze aktive code zuerst indem du Tests zu Modulen hinzufügst, die du bei Feature-Arbeit oder Bugfixes berührst
  • Reine Logik extrahieren aus schwer testbaren Dateien, damit Geschäftsregeln ohne Framework- oder Runtime-Rauschen getestet werden können
  • Seam-Wrapper hinzufügen um native Plugins, Netzwerk-Clients, Dateisystemaufrufe und Electron IPC
  • Brüchige Muster ablehnen bei der Einführung von Mocks. Tipps zu Best Practices für JavaScript-Tests sind hier besonders hilfreich, da sie das oft übersehene Problem des Over-Mocking und die daraus resultierenden instabilen Tests aufzeigen.

Das Ziel ist nicht sofortige Vollständigkeit, sondern eine stetige Verbesserung an den Stellen, an denen Regressionen das Team am meisten kosten.


Wenn dein Team Capacitor oder Electron Apps ausliefert und einen saubereren Release-Prozess für JavaScript-Änderungen benötigt, Capgo ist das eine Option. Es bietet Live Updates für CapacitorJS- und Electron-Apps, mit Rollout-Steuerung und Observability, sodass Teams solides Unit Testing mit einem sichereren Weg zum Ausliefern von Web-Bundle-Änderungen verbinden können, ohne für jeden Fix auf das Store-Review zu warten.

Live-Updates für Capacitor-Apps

Wenn ein Bug im Web-Layer live ist, liefere den Fix über Capgo aus, statt Tage auf die App-Store-Freigabe zu warten. Nutzer bekommen das Update im Hintergrund, während native Änderungen im normalen Review-Prozess bleiben.

Neues aus unserem Blog

Capgo gibt dir die besten Insights, um eine wirklich professionelle Mobile App zu erstellen.