Unit- und Komponententests, die Regressionen wirklich fangen
Jest Testing in React Native umfasst Unit-Tests für reine Logik und Hooks sowie Komponententests mit React Native Testing Library, die das tatsächliche Nutzerverhalten prüfen statt nur Implementierungsdetails abzugleichen. Richtig aufgesetzt, mit sauberem Mocking, disziplinierten Snapshots und Coverage-Schwellenwerten in der CI-Pipeline, fängt diese Testschicht die meisten Regressionen ab, bevor sie überhaupt einen Simulator erreichen.
Inhaltsverzeichnis
- 1. Wo Jest in der Testpyramide sitzt
- 2. Jest in einem React-Native-Projekt einrichten
- 3. Reine Logik und Hooks isoliert testen
- 4. Komponententests mit React Native Testing Library
- 5. Native Module und Abhängigkeiten mocken
- 6. Snapshot-Tests richtig einsetzen
- 7. Asynchrones Verhalten und Seiteneffekte testen
- 8. Coverage, CI und Team-Disziplin
- 9. Testebenen im Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Wo Jest in der Testpyramide sitzt
Jest Testing deckt in React Native zwei Ebenen der klassischen Testpyramide ab: Unit-Tests für reine Logik, Reducer und Custom Hooks, sowie Komponententests, die geprüfte UI-Komponenten in Isolation rendern und ihr Verhalten aus Nutzersicht verifizieren. Beide Ebenen unterscheiden sich klar von End-to-End-Tests mit Detox oder Maestro, die eine komplette App auf einem echten Gerät oder Simulator durchlaufen lassen.
Der Grund, warum Jest Testing auf diesen unteren Ebenen so wichtig ist, liegt in den Kosten: Ein Unit-Test läuft in Millisekunden, ein Komponententest in wenigen hundert Millisekunden, während ein vollständiger E2E-Test auf einem Simulator mehrere Sekunden bis Minuten benötigt. Die meisten Bugs, insbesondere Logikfehler und Regressionen bei UI-Verhalten, lassen sich weit günstiger auf der unteren Ebene abfangen als in einem langsamen, potenziell flakigen E2E-Lauf.
Das bedeutet nicht, dass E2E-Tests überflüssig sind, sie prüfen echte Integrationsszenarien über native Module und Navigation hinweg, die sich mit Jest allein nicht vollständig abdecken lassen. Aber ein gesundes Testverhältnis setzt auf viele schnelle Jest-Tests als Basis und wenige, gezielte E2E-Tests für kritische Nutzerflüsse an der Spitze.
2. Jest in einem React-Native-Projekt einrichten
Der Einstieg in Jest Testing beginnt mit dem passenden Preset. React-Native-CLI-Projekte nutzen üblicherweise das react-native-Preset, Expo-Projekte das jest-expo-Preset, das zusätzliche Mocks für Expo-spezifische native Module mitbringt. Beide Presets konfigurieren Babel-Transformationen so, dass JSX und moderne JavaScript-Syntax in der Testumgebung korrekt verarbeitet werden.
Ein häufiger Stolperstein beim ersten Setup ist transformIgnorePatterns: Viele node_modules-Pakete liefern unkompiliertes ESM-JavaScript aus, das Jest ohne explizite Ausnahme in der Standardkonfiguration ignoriert und dadurch mit Syntaxfehlern abbricht. Die Lösung ist eine erweiterte transformIgnorePatterns-Regel, die genau diese Pakete für die Babel-Transformation freigibt.
{
"preset": "react-native",
"setupFilesAfterEach": ["@testing-library/jest-native/extend-expect"],
"transformIgnorePatterns": [
"node_modules/(?!(react-native|@react-native|@react-navigation|react-native-reanimated)/)"
],
"collectCoverageFrom": [
"src/**/*.{js,jsx,ts,tsx}",
"!src/**/*.d.ts"
]
}
Ein zweiter typischer Fehler beim Setup: Native Module wie react-native-reanimated oder @react-native-async-storage/async-storage funktionieren in der Jest-Umgebung ohne Mock nicht, weil sie echten nativen Code voraussetzen, der in Node.js nicht existiert. Diese Mocks müssen explizit in einer Setup-Datei registriert werden, bevor überhaupt der erste Test läuft, sonst schlägt jeder Test fehl, der eine Komponente mit dieser Abhängigkeit importiert.
3. Reine Logik und Hooks isoliert testen
Der einfachste und günstigste Teil von Jest Testing sind Unit-Tests für Funktionen, Reducer und Selektoren, die keinerlei UI rendern. Diese Tests laufen extrem schnell, weil sie ohne React-Rendering-Overhead direkt Eingabe und erwartete Ausgabe vergleichen, was sie ideal für Business-Logik wie Preisberechnungen, Validierungsregeln oder Datenformatierung macht.
Für Custom Hooks bietet React Native Testing Library die Funktion renderHook, die einen Hook isoliert ausführt, ohne eine vollständige Komponente drumherum bauen zu müssen. Das ist besonders wertvoll für Hooks mit komplexer interner Zustandslogik, etwa einen Hook, der einen Formularzustand verwaltet oder wiederholte Anfragen mit Retry-Logik kapselt.
// useDebouncedValue.test.js
import { renderHook, act } from '@testing-library/react-native';
import { useDebouncedValue } from '../hooks/useDebouncedValue';
test('debounces value updates by the given delay', () => {
jest.useFakeTimers();
const { result, rerender } = renderHook(
({ value }) => useDebouncedValue(value, 300),
{ initialProps: { value: 'a' } }
);
expect(result.current).toBe('a');
rerender({ value: 'ab' });
// Value should not update immediately
expect(result.current).toBe('a');
act(() => {
jest.advanceTimersByTime(300);
});
// After the delay, the debounced value catches up
expect(result.current).toBe('ab');
jest.useRealTimers();
});
4. Komponententests mit React Native Testing Library
React Native Testing Library ist das zentrale Werkzeug für Jest Testing auf Komponentenebene und folgt einem klaren Prinzip: Tests sollen Komponenten so verwenden, wie ein echter Nutzer es tut, über sichtbaren Text, zugängliche Rollen oder testID-Attribute, statt über interne Implementierungsdetails wie State-Variablen oder Methodennamen.
Dieser Ansatz macht Tests robuster gegenüber Refactorings: Solange sich das sichtbare Verhalten einer Komponente nicht ändert, bleibt der Test grün, auch wenn die interne Implementierung komplett umgeschrieben wird. Ein Test, der stattdessen interne State-Werte prüft, bricht bei jedem Refactoring, unabhängig davon, ob sich das Nutzerverhalten tatsächlich geändert hat.
// LoginForm.test.js
import { render, screen, fireEvent } from '@testing-library/react-native';
import { LoginForm } from '../components/LoginForm';
test('shows a validation error when submitting an empty email', () => {
const onSubmit = jest.fn();
render(<LoginForm onSubmit={onSubmit} />);
fireEvent.press(screen.getByRole('button', { name: /log in/i }));
expect(screen.getByText(/email is required/i)).toBeVisible();
expect(onSubmit).not.toHaveBeenCalled();
});
test('calls onSubmit with entered credentials', () => {
const onSubmit = jest.fn();
render(<LoginForm onSubmit={onSubmit} />);
fireEvent.changeText(screen.getByPlaceholderText(/email/i), 'user@example.com');
fireEvent.changeText(screen.getByPlaceholderText(/password/i), 'secret123');
fireEvent.press(screen.getByRole('button', { name: /log in/i }));
expect(onSubmit).toHaveBeenCalledWith({ email: 'user@example.com', password: 'secret123' });
});
5. Native Module und Abhängigkeiten mocken
Sauberes Mocking ist eine Grundvoraussetzung für zuverlässiges Jest Testing in React Native, weil zahlreiche Abhängigkeiten echten nativen Code voraussetzen, der in der Jest-Testumgebung schlicht nicht existiert. AsyncStorage, Navigation-Objekte und native Module müssen deshalb konsequent mit jest.mock ersetzt werden, bevor eine Komponente, die sie verwendet, überhaupt gerendert wird.
Für Netzwerkaufrufe hat sich Mock Service Worker (msw) als robuste Alternative zu manuellen Fetch-Mocks etabliert, weil es auf Request-Handler-Ebene ansetzt, statt globale Funktionen zu überschreiben. Das macht Tests unabhängig von der konkreten HTTP-Client-Implementierung und funktioniert identisch, egal ob fetch, axios oder ein anderer Client verwendet wird.
// __mocks__/@react-native-async-storage/async-storage.js
import mockAsyncStorage from '@react-native-async-storage/async-storage/jest/async-storage-mock';
export default mockAsyncStorage;
// UserPreferences.test.js
jest.mock('@react-navigation/native', () => ({
useNavigation: () => ({ navigate: jest.fn(), goBack: jest.fn() }),
}));
test('persists a preference change to AsyncStorage', async () => {
const AsyncStorage = require('@react-native-async-storage/async-storage').default;
render(<UserPreferences />);
fireEvent.press(screen.getByRole('switch', { name: /dark mode/i }));
expect(await AsyncStorage.getItem('darkMode')).toBe('true');
});
6. Snapshot-Tests richtig einsetzen
Snapshot-Tests sind innerhalb von Jest Testing ein zweischneidiges Werkzeug. Sie liefern echten Mehrwert bei stabilen, rein präsentationalen Komponenten, deren Markup sich selten ändert, etwa einem Button oder einer Badge-Komponente. Ein Snapshot-Diff fällt dort sofort auf und zeigt exakt, was sich am gerenderten Output geändert hat.
Bei häufig sich ändernden Bildschirmen werden Snapshot-Tests hingegen schnell zu Rauschen-Generatoren: Entwickler gewöhnen sich an, Snapshot-Diffs reflexartig mit --ci -u zu aktualisieren, ohne den Unterschied tatsächlich zu prüfen. Damit verlieren Snapshots ihren eigentlichen Zweck als Regressionsschutz vollständig, weil jede unbeabsichtigte Änderung stillschweigend zum neuen "erwarteten" Zustand wird.
Eine diszipliniertere Alternative: Snapshot-Diffs im Pull-Request-Review explizit lesen und kommentieren, statt sie blind zu bestätigen, und Snapshots gezielt nur für Komponenten einsetzen, bei denen ein Markup-Diff tatsächlich aussagekräftig ist. Für Verhalten, das sich besser über konkrete Assertions prüfen lässt, sind gezielte getByText/getByRole-Prüfungen fast immer aussagekräftiger als ein pauschaler Snapshot.
7. Asynchrones Verhalten und Seiteneffekte testen
Ein Großteil des realen Jest Testing-Aufwands in React Native dreht sich um asynchrones Verhalten: Ladezustände, Fehlerzustände und Erfolgszustände von datenladenden Komponenten. Die Funktion waitFor aus React Native Testing Library wartet auf eine Bedingung, bis sie erfüllt ist, etwa bis ein Ladeindikator verschwindet und stattdessen die geladenen Daten sichtbar werden.
Für zeitbasiertes Verhalten wie Debouncing oder Polling-Intervalle sind Jest-Fake-Timer (jest.useFakeTimers()) unverzichtbar. Ohne sie müsste ein Test tatsächlich Sekunden real warten, was Testläufe unnötig verlangsamt und bei parallelen CI-Läufen zu Flakiness führen kann. Mit Fake-Timern lässt sich die Zeit gezielt vorspulen, ohne dass der Test tatsächlich Wartezeit verbraucht.
Ein häufiger Fehler bei asynchronen Tests: Vergessen, auf eine Promise-Auflösung zu warten, bevor eine Assertion geprüft wird. Das führt zu sogenannten "false green"-Tests, die grün durchlaufen, obwohl die eigentliche Assertion nie ausgeführt wurde, weil der Test bereits vorher endete. Konsequentes await waitFor(...) um jede Assertion, die von asynchronem State abhängt, verhindert dieses Muster zuverlässig.
8. Coverage, CI und Team-Disziplin
Coverage-Zahlen sind ein nützliches Signal für Jest Testing, aber kein Selbstzweck. In jest.config.js lassen sich Coverage-Schwellenwerte pro Datei oder Projekt festlegen, sodass ein Build fehlschlägt, wenn die Testabdeckung unter einen definierten Wert fällt. Diese Schwellenwerte sind sinnvoll als Frühwarnsystem, nicht als starre Vorgabe, die Entwickler zu bedeutungslosen Tests verleitet, nur um eine Zahl zu erreichen.
Die Integration in GitHub Actions oder GitLab CI läuft üblicherweise bei jedem Pull Request: Jest-Tests werden ausgeführt, ein Coverage-Report generiert und bei Unterschreitung des Schwellenwerts der Merge blockiert. Diese Automatisierung stellt sicher, dass Jest Testing nicht nur lokal, sondern konsequent vor jedem Merge in den Hauptzweig läuft.
Ein sinkender Coverage-Trend über mehrere Pull Requests hinweg ist ein wertvolleres Signal als eine einzelne absolute Zahl. Teams, die Coverage als hartes Gate ohne Kontext behandeln, laufen Gefahr, Coverage-Theater zu produzieren, oberflächliche Tests, die zwar Zeilen ausführen, aber keine echte Assertion über das erwartete Verhalten treffen.
9. Testebenen im Vergleich
Jede Testebene hat ein anderes Verhältnis aus Geschwindigkeit, Aussagekraft und Flakiness-Risiko. Die folgende Übersicht ordnet die wichtigsten Optionen für Jest Testing und E2E-Tests ein.
| Testebene | Geschwindigkeit | Aussagekraft | Typischer Einsatz |
|---|---|---|---|
| Reine Unit-Tests | Sehr schnell | Hoch für Logik | Reducer, Validierung, Formatierung |
| Komponententests (RNTL) | Schnell | Hoch für UI-Verhalten | Formulare, Interaktionslogik |
| Snapshot-Tests | Sehr schnell | Niedrig bei häufigen Änderungen | Stabile, präsentationale Komponenten |
| E2E-Tests (Detox) | Langsam | Sehr hoch für Integration | Kritische End-to-End-Nutzerflüsse |
Ein gesundes Testportfolio setzt auf viele schnelle Unit- und Komponententests als Basis, gezielte Snapshot-Tests nur dort, wo sie tatsächlich Signal liefern, und wenige E2E-Tests für die kritischsten Nutzerflüsse. Jest Testing bildet damit das Fundament, auf dem die restliche Teststrategie aufbaut.
Mironsoft
Testautomatisierung und CI-Setup für React-Native-Projekte
Regressionen abfangen, bevor Nutzer sie sehen?
Wir richten Jest und React Native Testing Library sauber ein, entwickeln aussagekräftige Komponententests statt Snapshot-Rauschen und integrieren Coverage-Schwellenwerte fest in eure CI-Pipeline.
Test-Setup
Jest-Konfiguration, Mocks und Presets korrekt für euer Projekt aufsetzen
Komponententests
Verhalten statt Implementierungsdetails testen, mit React Native Testing Library
CI-Integration
Coverage-Schwellenwerte und automatische Testläufe pro Pull Request
10. Zusammenfassung
Jest Testing in React Native ist die Grundlage, auf der eine belastbare Teststrategie steht: Unit-Tests für reine Logik, Komponententests mit React Native Testing Library für Nutzerverhalten, sauberes Mocking für native Abhängigkeiten und disziplinierte Snapshot-Nutzung nur dort, wo sie tatsächlich Signal liefert. Zusammen fangen diese Ebenen die meisten Regressionen ab, lange bevor ein langsamer, potenziell flakiger E2E-Test überhaupt startet.
Der entscheidende Unterschied zwischen wertvollem und wertlosem Jest Testing liegt in der Disziplin: Tests, die Nutzerverhalten statt Implementierungsdetails prüfen, Coverage als Signal statt als Zwang behandeln und Snapshot-Diffs tatsächlich lesen, statt sie reflexartig zu aktualisieren. Diese Disziplin, konsequent in der CI-Pipeline verankert, macht Testautomatisierung zu einem echten Sicherheitsnetz statt zu einer Formalität.
React Native Testing mit Jest: Das Wichtigste auf einen Blick
Testpyramide
Viele schnelle Unit- und Komponententests als Basis, wenige E2E-Tests für kritische Flows an der Spitze.
Verhalten statt Details
React Native Testing Library prüft sichtbares Nutzerverhalten, nicht interne Implementierung.
Sauberes Mocking
AsyncStorage, Navigation und native Module konsequent mocken, bevor Komponenten gerendert werden.
Coverage als Signal
Schwellenwerte in der CI-Pipeline als Frühwarnsystem, nicht als Einladung zu Coverage-Theater.