Integrationstests vs. End-to-End-Tests
Integrationstests vs End-to-End-Tests für Web-Apps: klare Definitionen, JS-Tools und wann Vitest, MSW, Playwright oder Cypress passen.
Integrationstests überprüfen, ob eigene Komponenten und Module zusammenarbeiten — sie laufen in CI in Millisekunden und mocken externe Systeme; End-to-End-Tests steuern die gesamte laufende Applikation über die echte Benutzeroberfläche wie ein echter Nutzer, laufen gegen einen deploytes oder Staging-Build und mocken nichts. Diese eine Unterscheidung löst den Großteil der Verwirrung, verbirgt aber ein zweites Problem, das spezifisch für die Frontend-Entwicklung ist: Das Wort „Integration” bedeutet für einen JavaScript-Entwickler etwas anderes als für einen Backend-QA-Engineer, und die Grenze zwischen „Integration” und „E2E” im Browser ist tatsächlich fließend.
Dieser Artikel zieht diese Grenze in Web-Stack-Begriffen. Er liefert präzise Definitionen mit konkreten Beispielen, einen Vergleich anhand der Kriterien, die tatsächlich entscheidend sind, zeigt, wo jeder Testtyp im Verhältnis zu Unit-Tests steht, welche JavaScript-Tools jeweils zugeordnet werden (Vitest, Jest, Testing Library, MSW, Playwright, Cypress), und gibt eine klare Empfehlung zur Aufteilung des Test-Budgets — keine ausweichende „Beides nutzen”-Antwort.
Wichtigste Erkenntnisse
- Integrationstests rendern eigene Module zusammen mit gemocktem Netzwerk und laufen in CI in Millisekunden; End-to-End-Tests steuern die deployete App in einem echten Browser und mocken nichts.
- In einer JavaScript-Applikation bedeutet „Integration” in der Regel Vitest oder Jest plus Testing Library und MSW; „End-to-End” bedeutet in der Regel Playwright oder Cypress gegen die echte URL.
- Die Frontend-Grenze zwischen Integration und E2E ist ein Spektrum, keine Mauer — wo man sie zieht, ist eine Budget-Entscheidung, kein Gesetz.
- Schreibe überwiegend Integrationstests für das beste Verhältnis von Aussagekraft zu Kosten, halte eine kleine Suite von drei bis fünf E2E-Tests für die wertvollsten User Journeys vor, und versuche nicht, alles per E2E abzudecken.
- Ein Produktionsfehler ist per Definition eine Journey, die die Tests nie geprüft haben — ein Session Replay eines echten Fehlers ist daher der schnellste Weg zum fehlenden Test.
Integrationstests vs. End-to-End-Tests auf einen Blick
Die beiden Testtypen unterscheiden sich in jeder Dimension, die für die Budgetplanung relevant ist: wie viel sie abdecken, wie schnell sie laufen, wo sie laufen, was sie faken, wie oft sie aus falschen Gründen fehlschlagen und welche Fehler nur sie erkennen können.
| Kriterium | Integrationstest | End-to-End (E2E)-Test |
|---|---|---|
| Umfang | Mehrere eigene Module zusammen (z. B. eine Komponente + ihre Datenschicht) | Die gesamte laufende App, von vorne bis hinten |
| Geschwindigkeit | Millisekunden bis wenige Sekunden | Sekunden bis Minuten pro Test |
| Ausführungsort | Lokal und in CI, in Node oder jsdom | Gegen einen deploytes oder Staging-Build, in einem echten Browser |
| Was gemockt wird | Externe Systeme — das Netzwerk, Drittanbieter-APIs | Nichts (oder so wenig wie möglich) |
| Flakiness | Gering — kein echtes Netzwerk, kein echtes Browser-Timing | Höher — hängt davon ab, dass das Gesamtsystem stabil bleibt |
| Kosten / Wartung | Geringer beim Schreiben und Grünhalten | Höher; Suites veralten mit der Weiterentwicklung der App |
| Erkennt exklusiv | Defekte API-Verträge, falsch geformte Responses, State-Verdrahtung | Auth, Redirects, Env-Konfiguration, Drittanbieter-Skripte, seitenübergreifenden State |
Was ein Integrationstest ist — mit einem Web-Beispiel
Discover how at OpenReplay.com.
Ein Integrationstest rendert einen Baum eigener Komponenten zusammen und prüft ihr kombiniertes Verhalten mit gestubtem Netzwerk — er verifiziert, dass die bereits unit-getesteten Einheiten tatsächlich zusammenarbeiten. Er startet keinen Browser und trifft keinen echten Server.
Ein konkretes Beispiel: eine LoginForm-Komponente, die mit einem Authentifizierungs-Hook und einem Profil-Fetch verdrahtet ist. Der Integrationstest rendert das Formular, füllt die Felder aus, sendet es ab und prüft, ob der Willkommens-Zustand erscheint — aber die Aufrufe POST /api/login und GET /api/profile werden abgefangen und von einem Mock beantwortet. Getestet wird die Verdrahtung zwischen dem Formular, dem Hook, der Request-Schicht und dem gerenderten Ergebnis. Das echte Backend, der Auth-Provider oder das CDN werden dabei nicht getestet.
Dies ist die Schicht, auf der Vertragsfehler sichtbar werden: Die Komponente erwartet user.name, aber der Handler gibt user.fullName zurück, ein 401-Pfad rendert das Fehlerbanner nie neu, oder ein Lade-Flag wird nie zurückgesetzt. Integrationstests fangen die Fehler ab, die zwischen den eigenen Modulen leben — defekte API-Verträge, falsch geformte Responses, State-Verdrahtung — ohne die Kosten eines echten Deployments.
Was ein End-to-End-Test ist — mit einem Web-Beispiel
Ein End-to-End-Test steuert die tatsächlich deployete Applikation in einem echten Browser, genau wie ein Nutzer es tun würde, und prüft, was der Nutzer sieht — ohne jegliches Mocking. Er übt den echten Server, die echte Datenbank, den echten Auth-Flow, echte Redirects und alle Drittanbieter-Skripte aus, die die Seite lädt.
Das kanonische Beispiel ist ein Nutzer, der sich einloggt und einen Kauf abschließt: zur Live-URL navigieren, Zugangsdaten eingeben, absenden, auf dem Dashboard landen, einen Artikel in den Warenkorb legen, die Zahlung abschließen und die Bestellung bestätigen. Jede Schicht ist beteiligt — Frontend-Bundle, Netzwerk, API, Datenbank, Session-Cookies, Zahlungsintegration.
E2E-Tests erkennen Fehler, die nur in einem echten Deployment existieren: Authentifizierung und Redirects, Umgebungskonfiguration, Drittanbieter-Skripte, Content-Security-Policy-Verstöße und seitenübergreifenden State, der eine Navigation überlebt. Keiner dieser Fehler reproduziert sich, wenn das Netzwerk gemockt ist — genau deshalb ist E2E seinen höheren Preis für die Journeys wert, die Umsatz generieren.
Einordnung: Pyramid und Trophy
Beide Testtypen liegen oberhalb von Unit-Tests, die eine einzelne Funktion oder Komponente isoliert prüfen. Die klassische Testing Pyramid schreibt viele Unit-Tests an der Basis vor, weniger Integrationstests in der Mitte und sehr wenige langsame E2E-Tests an der Spitze. Das ist ein solider Standard, um eine Suite schnell zu halten.
Das moderne Gegenmodell ist das Testing Trophy, popularisiert von Kent C. Dodds, das die Integrationsschicht verbreitert, weil Integrationstests für Web-Applikationen das beste Verhältnis von Aussagekraft zu Kosten bieten — nah an echter Nutzung, aber ohne die Flakiness und Laufzeit vollständiger E2E-Tests. Die Begründung stützt sich auf das Leitprinzip von Testing Library: Je mehr deine Tests der Art ähneln, wie deine Software verwendet wird, desto mehr Vertrauen können sie dir geben. Ein Integrationstest, der einen echten Komponentenbaum rendert und nutzersichtbares Verhalten prüft, ähnelt der echten Nutzung weit mehr als ein Unit-Test eines isolierten Reducers — und das in Millisekunden. Das Trophy ist als weit verbreitete moderne Sichtweise für Web-Apps zu verstehen, nicht als universeller Ersatz für die Pyramid.
Die fließende Frontend-Grenze
In der Frontend-Entwicklung ist die Grenze zwischen Integration und E2E ein Spektrum, keine Mauer: Ein Testing Library-Test, der einen Komponenten-Teilbaum rendert, ist „Integration”; ein Playwright-Test, der die echte URL lädt, ist „E2E”; und wo man die Linie zieht, ist eine Budget-Entscheidung, kein Gesetz. Diese fließende Grenze ist die größte Quelle der Verwirrung, weil dasselbe Wort in verschiedenen Kontexten Unterschiedliches bedeutet.
Die Backend-QA-Literatur unterteilt Integrationstests in Bottom-up-, Top-down- und Big-Bang-Strategien — diese Taxonomie ist real, aber sie ist Server-Integrations-Jargon und hilft selten jemandem, der in Vitest und Playwright arbeitet. Ignoriere sie. Was für einen Web-Entwickler zählt, ist die praktische Zuordnung: „Integration” bedeutet fast immer Rendern eines Komponentenbaums mit gemocktem Netzwerk, und „E2E” bedeutet fast immer Steuern der deploytes App in einem echten Browser. Die Grauzone dazwischen — etwa ein Playwright-Test, der auf einen lokalen Dev-Server zeigt und MSW zum Abfangen des Netzwerks nutzt — ist in Ordnung; entscheide nur bewusst, welcher Seite des Budgets er zugeordnet wird.
Die JS-Toolchain für jeden Typ, mit Code
In einer JavaScript-Applikation bedeutet „Integrationstest” in der Regel das Rendern eines Komponentenbaums und das Prüfen seines Verhaltens mit gemocktem Netzwerk — typischerweise Vitest oder Jest plus Testing Library und MSW — während „End-to-End-Test” bedeutet, die deployete App in einem echten Browser mit Playwright oder Cypress zu steuern.
Mock Service Worker (MSW) ist das entscheidende Element, das Integrationstests realistisch macht, ohne ein echtes Backend zu benötigen. Er fängt Requests auf der Netzwerkschicht in beiden Umgebungen ab — über die Service Worker API im Browser (setupWorker) und über Low-Level-Request-Interception in Node (setupServer, das Vitest- oder Jest-Tests verwenden und das keine Service-Worker-Datei benötigt). Da die Interception an der Grenze stattfindet und nicht durch Patchen von fetch, können dieselben Handler Integrationstests, den Dev-Server und sogar Playwright-Läufe antreiben; die Dokumentation bringt es direkt auf den Punkt: „Imagine using the same API mocks during development, integration and end-to-end testing, and then in your Storybook.”
Hier ist dasselbe Login-und-Profil-Abrufen-Feature als Integrationstest. Beachte die MSW v2 http + HttpResponse API, die das v1-Muster mit res/ctx abgelöst hat:
// login.integration.test.tsx — Vitest 4 + @testing-library/react 16 + MSW 2
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { http, HttpResponse } from 'msw'
import { setupServer } from 'msw/node'
import { LoginForm } from './LoginForm'
const server = setupServer(
http.post('/api/login', () =>
HttpResponse.json({ token: 'abc' })
),
http.get('/api/profile', () =>
HttpResponse.json({ name: 'Ada' })
)
)
beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())
test('logs in and shows the profile name', async () => {
render(<LoginForm />)
await userEvent.type(screen.getByLabelText(/email/i), 'ada@example.com')
await userEvent.type(screen.getByLabelText(/password/i), 'hunter2')
await userEvent.click(screen.getByRole('button', { name: /sign in/i }))
expect(await screen.findByText(/welcome, ada/i)).toBeInTheDocument()
})
Es wird kein Browser gestartet, kein Server läuft, und das Netzwerk ist vollständig kontrolliert — dieser Test ist daher schnell und deterministisch. Dasselbe Feature als E2E-Test läuft gegen die deployete App und mockt nichts. Playwright steuert Chromium, Firefox und WebKit mit einer einzigen API, mit Auto-Waiting und web-first Assertions:
// login.e2e.spec.ts — Playwright 1.6x
import { test, expect } from '@playwright/test'
test('user logs in and sees their profile', async ({ page }) => {
await page.goto('/login')
await page.getByLabel('Email').fill('ada@example.com')
await page.getByLabel('Password').fill('hunter2')
await page.getByRole('button', { name: 'Sign in' }).click()
await expect(
page.getByRole('heading', { name: /welcome, ada/i })
).toBeVisible()
})
Die Assertions sehen ähnlich aus; was sich unterscheidet, ist alles darunter. Die Playwright-Spec beweist, dass der echte Auth-Endpoint, der Redirect und der Session-Cookie in einem echten Browser funktionieren — und zahlt dafür mit Laufzeit und Flakiness. Eine praktische Einschränkung, die es wert ist zu kennen: Da asynchrone React Server Components neu sind, unterstützt Jest sie derzeit nicht, und Next.js empfiehlt E2E-Tests für asynchrone Komponenten — ein konkreter Fall, in dem die Integrationsschicht noch nicht ausreicht und E2E seinen Platz verdient.
Eine Entscheidungsregel nach Szenario
Wähle den Testtyp anhand des Fehlers, den du verhindern möchtest, nicht aus Gewohnheit. Die Regel:
- Neue Modul-Interaktion oder ein Frontend-Backend-Vertrag → Integrationstest. Eine Komponente mit einem neuen Endpoint verdrahten, einen neuen Reducer mit einem neuen Selector, ein Formular mit einer Validierungsschicht: Den Baum rendern, das Netzwerk mit MSW mocken, das Verhalten prüfen.
- Eine kritische User Journey über den gesamten Stack → ein E2E-Test. Login, Registrierung, Checkout — der eine Flow, dessen Ausfall Geld oder Vertrauen kostet.
- Alles andere → auf Integration setzen und es nicht per E2E abdecken.
Die praktische Empfehlung für ein Web-Team: Schreibe überwiegend Integrationstests für das beste Verhältnis von Aussagekraft zu Kosten, halte eine kleine Suite von drei bis fünf End-to-End-Tests für die wertvollsten User Journeys vor, und versuche nicht, alles per E2E abzudecken. Ein E2E-Test für einen Tooltip ist eine Wartungslast; ein Integrationstest dafür ist günstige Absicherung.
Warum E2E-Suites veralten und wie man sie stabil hält
End-to-End-Suites veralten, weil jeder Test davon abhängt, dass das gesamte System stabil bleibt; die Lösung besteht darin, sie klein zu halten, auf nutzersichtbare Ergebnisse statt auf Implementierungsdetails zu prüfen und diejenigen zu löschen, die keinen Umsatz mehr schützen. Eine umbenannte Test-ID, ein langsameres Staging-Deployment, ein instabiles Drittanbieter-Widget oder ein verschobener Redirect können eine bisher grüne Suite rot werden lassen, ohne dass eine echte Regression vorliegt — und eine Suite, die falschen Alarm schlägt, wird ignoriert. Konkret:
- Rollen- und Label-basierte Selektoren gegenüber fragilen CSS-Pfaden bevorzugen;
- Playwrights Auto-Waiting statt fester Wartezeiten nutzen;
- Testdaten isolieren; und
- die Anzahl begrenzen, damit die Suite schnell genug bleibt, um ihr zu vertrauen.
Keine Suite deckt jedoch Journeys ab, die man sich nicht vorgestellt hat — und ein Produktionsfehler ist per Definition ein Pfad, den Integrations- und E2E-Tests nie geprüft haben. Session Replay zahlt sich genau hier aus: Ein Replay eines echten Fehlers zeigt die exakte, nicht getestete Journey, die fehlgeschlagen ist — den DOM-Zustand, die Netzwerkaufrufe und die Konsolenfehler im Moment des Versagens. Session Replays von Login- und Checkout-Flows decken häufig Fehlermodi auf, die kein lokaler Test je ausgeübt hat — etwa einen umgebungsspezifischen Redirect oder ein durch CSP blockiertes Drittanbieter-Skript. Der professionelle Ansatz besteht darin, diese Reproduktion in den fehlenden Test umzuwandeln: Ein Journey-Level-Fehler wird zu einer neuen Playwright-Spec, ein Komponenten- oder Vertragsfehler zu einem neuen Testing Library + MSW Integrationstest — und derselbe Fehler kann nicht erneut auftreten.
Fazit
Die Aufteilung, die sich in der Produktion bewährt, ist: überwiegend Integration, wenige hochwertige E2E-Tests und Unit-Tests darunter für reine Logik — Integration, weil sie die meiste Aussagekraft pro Sekunde Laufzeit bietet; E2E, weil manche Fehler nur in einem echten deploytes Browser existieren. Beginne damit, Integrationstests mit Vitest oder Jest, Testing Library und MSW für die Modulgrenzen des nächsten Features zu schreiben, reserviere Playwright für die Handvoll Journeys, die du dir nicht leisten kannst zu brechen, und lass echte Produktionsfehler dir zeigen, welchen Test du vergessen hast zu schreiben.
FAQs
Kann ein einzelnes Tool wie Playwright oder Cypress sowohl Integrations- als auch End-to-End-Tests schreiben?
Ja, aber die Unterscheidung ergibt sich daraus, wie der Test abgegrenzt wird, nicht aus dem Tool. Playwright und Cypress können eine Komponente isoliert mit gemocktem Netzwerk mounten, was effektiv einem Integrationstest entspricht, oder die deployete URL laden und den gesamten Stack ausüben, was End-to-End ist. In beiden Tools existieren Component-Testing-Modi, während Vitest oder Jest mit Testing Library der gängigere Integrationspfad im JavaScript-Ökosystem bleibt.
Benötige ich die Datei mockServiceWorker.js für MSW in Vitest- oder Jest-Integrationstests?
Nein. Die Datei mockServiceWorker.js wird nur für den Browser-Pfad mit setupWorker benötigt, der auf der Service Worker API basiert. Node-basierte Integrationstests laufen über setupServer, das Requests mittels Low-Level-Klassen-Erweiterung abfängt und daher keine generierte Datei benötigt. Das Worker-Skript wird nur generiert, wenn Requests in einem echten Browser gemockt werden sollen, etwa während der lokalen Entwicklung oder bei browserbasierten Testläufen.
Wann sollte ich einen End-to-End-Test statt eines Integrationstests schreiben?
Schreibe einen End-to-End-Test, wenn der zu verhindernde Fehler nur in einem echten Deployment existiert — Authentifizierung, Redirects, Umgebungskonfiguration, Session-Cookies, Drittanbieter-Skripte, Content-Security-Policy-Verstöße oder seitenübergreifender State. Diese reproduzieren sich nie, wenn das Netzwerk gemockt ist. Für alles, was zwischen eigenen Modulen liegt — defekte API-Verträge, falsch geformte Responses oder State-Verdrahtung — ist ein Integrationstest schneller, günstiger und weniger fehleranfällig. Reserviere End-to-End-Tests für die wertvollsten umsatzgenerierenden Journeys.
Warum bestehen meine End-to-End-Tests lokal, schlagen aber in CI fehl?
End-to-End-Suites hängen davon ab, dass das gesamte System stabil bleibt — Unterschiede zwischen lokaler und CI-Umgebung lassen sie daher fehlschlagen, ohne dass eine echte Regression vorliegt. Häufige Ursachen sind feste Wartezeiten statt Auto-Waiting, fragile CSS-Selektoren, die sich zwischen Builds verschieben, langsameres CI-Deployment-Timing, gemeinsam genutzte oder nicht isolierte Testdaten und instabile Drittanbieter-Widgets. Bevorzuge rollen- und Label-basierte Selektoren, nutze Playwrights Auto-Waiting, isoliere Testdaten pro Lauf und halte die Suite klein genug, um zuverlässig zu bleiben.