Wie wir MailVault testen: 600+ Tests, echte E-Mails, echte Server
E-Mail ist eines dieser Dinge, die an der Oberfläche einfach aussehen und alles andere als das sind. IMAP-Server verhalten sich unterschiedlich. Die MIME-Kodierung hat Sonderfälle, die älter sind als die Laufbahn der meisten Entwickelnden. OAuth2-Token laufen zu ungünstigen Zeiten ab. Anhänge können in mehrteiligen Nachrichten drei Ebenen tief verschachtelt sein. Wer einen E-Mail-Client baut, testet entweder besessen oder liefert Fehler aus.
MailVault hat derzeit über 600 automatisierte Tests, verteilt auf vier Schichten: Rust-Unit-Tests, JavaScript-Unit-Tests, Integrationstests gegen echte IMAP-Server und End-to-End-Tests, die die echte Desktop-App steuern. So greift das alles ineinander.
Schicht 1: Rust-Unit-Tests (55 Tests)
Der Kern von MailVault ist in Rust geschrieben — alle IMAP-Verbindungen, der SMTP-Versand, das MIME-Parsing und die Verwaltung von OAuth2-Token laufen nativ, nicht über einen Node.js-Sidecar. Die Rust-Schicht hat 55 Unit-Tests für die Funktionen, die am schwersten richtig hinzubekommen sind.
Ein gutes Beispiel ist die Kompression von UID-Bereichen. Wenn MailVault Kopfzeilen für die UIDs [1, 2, 3, 5, 6, 10] holen muss, komprimiert es sie zum IMAP-Bereichsstring "1:3,5:6,10", um den Befehl klein zu halten. Die Tests decken leere Eingaben, einzelne UIDs, zusammenhängende Bereiche, Lücken, unsortierte Eingaben, Duplikate, große Bereiche und die Umkehrung ab (Bereiche wieder zu einzelnen UIDs auffalten). Es gibt außerdem Roundtrip-Tests, die prüfen, dass compress → expand die ursprüngliche Menge zurückgibt. Das zählt, weil gestückelte UID-FETCH-Befehle kaputtgehen, wenn die Bereiche falsch sind.
Das Modul für die Microsoft-Graph-API hat eigene Tests, die das Parsen von Graph-JSON-Antworten in MailVaults internes Format EmailHeader prüfen, und die Tests des Hauptmoduls decken das Parsen von Maildir-Dateinamen, das Kodieren von Markierungen und das Nachschlagen von Dateien über die UID ab.
Schicht 2: JavaScript-Unit-Tests (13 Testsuiten)
Das Frontend hat 13 eigene Unit-Test-Suiten auf Vitest, dazu weitere Tests auf Store-Ebene. Sie decken die Logik zwischen dem Rust-Backend und der React-Oberfläche ab:
Das Parsen von E-Mail ist der am gründlichsten getestete Bereich. Die Suite emailParser prüft das Herausziehen von Korrespondenten (das Zerlegen von "John Doe <[email protected]>" in Name und Adresse), das Normalisieren von Betreffzeilen (das Entfernen aller Kombinationen von Re:, Fwd:, FW:, Re[N]:), das Erkennen von Signaturen und das Bestimmen der Zitatebene. Der Threading-Algorithmus — eine vereinfachte Fassung von Jamie Zawinskis Threading-Spezifikation — hat Tests für In-Reply-To-Ketten, das Durchlaufen der References-Kopfzeile, das Zusammenfassen über den Betreff als Rückfallweg und das Erkennen von Zyklen bei fehlerhaften Kopfzeilen.
Weitere Suiten testen die Hintergrund-Pipeline (Nebenläufigkeitssteuerung, erneute Versuche mit exponentiellem Zurückweichen, die Kaskadenreihenfolge zwischen aktivem Konto und Hintergrundkonten), die Handhabung von OAuth2-Token (vorsorgliches Erneuern vor Ablauf, Zusammenlegen gleichzeitiger Erneuerungsversuche), die Optimierungen beim Delta-Sync (den vierstufigen CONDSTORE-Entscheidungsbaum), den Schnellladeweg (zwischengespeicherte Kopfzeilen sofort anzeigen, der Hintergrundabgleich trägt Änderungen nach), das widerstandsfähige Laden (sanftes Abfallen, wenn IMAP-Verbindungen mitten im Abruf abreißen) und die Zustandsübergänge im Zustand-Store.
Die Store-Tests selbst prüfen das Verdrängen im LRU-Cache (MailVault hält E-Mail-Texte mit einstellbarer Grenze im Arbeitsspeicher), das Entfernen von Feldern (rawSource und Anhangsinhalte werden vor dem Zwischenspeichern entfernt, um Speicher zu sparen) und das Abschätzen der Cache-Größe.
Schicht 3: Integrationstests gegen echte IMAP-Server
Unit-Tests mit Mocks sagen dir nur begrenzt viel. MailVaults Integrationstests verbinden sich mit echten IMAP-Servern mit echten Zugangsdaten und versenden echte E-Mails. Wir betreiben zwei eigene Testkonten auf einer privaten Domain, und jeder Integrationstestlauf spricht per TLS mit diesen Konten.
Die Kern-Suite email.test.js läuft einen vollständigen Zyklus: per IMAP verbinden, per SMTP eine E-Mail senden (mit Nodemailer), auf ihr Eintreffen warten, sie mit mailparser holen und parsen, Kopfzeilen und Text prüfen und die Testnachricht beim Aufräumen wieder löschen. Das fängt Probleme aus der Praxis, die Mocks entgehen — Eigenheiten bei der TLS-Aushandlung, serverspezifische IMAP-Erweiterungen, Unterschiede bei der Kodierung.
Die Suite für IMAP-Optimierungen testet MailVaults fortgeschrittene Protokollfunktionen gegen den echten Server: das Aushandeln von COMPRESS=DEFLATE (unterstützt der Server es wirklich? funktioniert die komprimierte Sitzung?), den Delta-Sync per CONDSTORE (liefert CHANGEDSINCE die richtigen geänderten Markierungen zurück?) und kompakte ESEARCH-Antworten.
Die Suite für kontoübergreifende E-Mail testet Szenarien mit mehreren Konten: von Konto A an Konto B senden, prüfen, dass die Nachricht im Posteingang von B erscheint, das Weiterreichen des Gelesen-/Ungelesen-Status testen und kontrollieren, dass der Threading-Algorithmus die Unterhaltung über beide Konten hinweg korrekt verknüpft.
Die Saat aus 500 E-Mails
Um realistisch zu testen, befüllen wir unsere Test-Postfächer mit 500 E-Mails — 250 in jede Richtung zwischen den beiden Konten. Das Seeder-Skript erzeugt E-Mails mit realistischen Betreffzeilen ("Q4 Revenue Report — Final Numbers", "Re: Bug in checkout flow", "Invoice #4821 — Due January 15") und wechselt durch fünf MIME-Typen: reiner Text, HTML, multipart/alternative, HTML mit einem Anhang und HTML mit mehreren Anhängen (CSV, JSON und PNG).
Etwa ein Drittel der eingespielten E-Mails ist als ungelesen markiert. Zu den Anhängen gehören generierte PDF-Berichte, CSV-Datendateien, JSON-Metadaten und PNG-Bilder. Das ergibt ein Postfach, das aussieht und sich verhält wie ein echter Posteingang — gemischte Inhaltstypen, unterschiedliche Lesezustände, verschachtelte Unterhaltungen und genug Volumen, um Performanceprobleme sichtbar zu machen.
Stresstests
Die Suite mailbox-stress.test.js geht weiter: Sie schleust 500 E-Mails in ein einzelnes Postfach ein (im Wechsel über alle fünf MIME-Typen) und testet dann, dass die IMAP-Schicht von MailVault die Menge korrekt bewältigt. Das fängt Probleme mit gestückeltem UID FETCH (wir teilen Abrufe in Blöcke zu 200, um die Längenbegrenzung von IMAP-Befehlen zu umgehen), mit der Blätterung und mit dem Speicherverbrauch unter Last.
Schicht 4: End-to-End-Tests in der echten App
Die letzte Ebene steuert die tatsächliche Tauri-Desktop-App über WebdriverIO mit dem Tauri-WebDriver. Die App wird mit dem Feature-Flag webdriver kompiliert, als echtes Desktop-Fenster gestartet, und die Tests bedienen sie so, wie eine Person es täte — Buttons klicken, in Felder tippen, auf das Laden von E-Mails warten.
Wir teilen die E2E-Tests in zwei Suiten. Die UI-Suite (6 Testdateien) testet alles, was keine IMAP-Verbindung braucht: Einstellungsbereiche, Interaktionen in der Seitenleiste, Tastenkürzel, Layout- und Ansichtswechsel sowie Vorlagen zum Verfassen. Die laufen schnell, weil sie das Netzwerk nicht anfassen.
Die verbundene Suite (11 Testdateien) testet die vollständige App mit echten Konten — darunter eines mit über 17.000 E-Mails. Das ist kein künstlicher Benchmark; es ist ein echtes Postfach mit über Jahre angesammelten Nachrichten, gemischten MIME-Typen, verschachtelten Threads und all den unordentlichen Sonderfällen, die zu langlebigen E-Mail-Konten gehören. E2E-Tests gegen ein Postfach dieser Größe halten uns bei Blätterung, Speicherverbrauch und gefühlter Geschwindigkeit im Großen ehrlich. Zu diesen Szenarien gehören:
Kaltstart — die App startet ohne Cache, fügt ein Konto hinzu und misst, wie lange es dauert, bis E-Mails erscheinen. Das ist ein Richtwert, den wir verfolgen: die ersten sichtbaren E-Mails aus einem völlig kalten Zustand.
Kontoverwaltung — Konten hinzufügen, zwischen ihnen wechseln, prüfen, dass der Zustand je Konto (zuletzt gewähltes Postfach, zwischengespeicherte E-Mail-Liste) korrekt erhalten bleibt.
E-Mail-Liste und -Ansicht — durch die E-Mail-Liste scrollen, E-Mails auswählen, prüfen, dass die Texte korrekt laden (aus dem Cache, aus dem Maildir oder ersatzweise per IMAP), kontrollieren, dass benachbarte E-Mails vorab geladen werden.
Verfassen, Antworten und Weiterleiten — das Verfassen-Fenster öffnen, auf eine E-Mail antworten (prüfen, dass der zitierte Text und die Empfänger stimmen), mit erhaltenen Anhängen weiterleiten.
Suche — die Suchleiste gegen das vorbefüllte Postfach testen, Trefferqualität und Geschwindigkeit prüfen.
Ordneroperationen — E-Mails zwischen Ordnern verschieben, archivieren, löschen (wobei UID EXPUNGE nach RFC 4315 nur die gemeinte Nachricht entfernt).
Gemeinsamer Posteingang — die kontoübergreifende Ansicht testen, die E-Mails aus allen Konten zu einer einzigen chronologischen Liste zusammenführt.
Leistungsmessungen — Kaltstartzeit, Latenz beim Laden von E-Mails, Threading-Geschwindigkeit. Sie geben eine Übersichtstabelle mit p50-, p95- und Maximalwerten aus, damit wir Rückschritte bemerken.
Jeder verbundene E2E-Test hat eine Sicherung: eine Positivliste zugelassener Testkonten. Versucht ein Test versehentlich, ein Konto zu verändern, das nicht auf dieser Liste steht, schlägt er sofort fehl. Das verhindert, dass Testcode während der Entwicklung je echte Nutzerdaten anfasst.
Warum das zählt
MailVault verwaltet deine E-Mails. Das sind persönliche, wichtige Daten. Wenn du ein Konto hinzufügst, den Ordner wechselst, eine Nachricht archivierst oder deinen Posteingang durchsuchst, muss es funktionieren — nicht nur im Idealfall, sondern auch, wenn der Server langsam ist, wenn die Verbindung abreißt, wenn eine E-Mail fehlerhafte Header hat, wenn ein OAuth2-Token mitten im Abruf abläuft.
Der vierschichtige Ansatz gibt uns auf jeder Ebene Sicherheit: Rust-Tests prüfen die Primitive auf niedriger Ebene, JS-Unit-Tests prüfen die Anwendungslogik, Integrationstests prüfen, dass echte Server sich so verhalten, wie wir es erwarten, und E2E-Tests prüfen, dass das Ganze als Desktop-App funktioniert. Wenn wir ein Release ausliefern, sind sie alle durchgelaufen.