GraphQL-Kontrakttests
Übersicht
Tests, die auf der Naht zwischen Schichten sitzen und brechen, wenn sich eine Seite ohne Zustimmung der anderen bewegt – tests-as-a-control angewandt auf Grenzen. Die goldene Regel: Teste den Kontrakt – die Datenform und Zugriffssemantik, auf die der Konsument angewiesen ist – niemals den Abfragestring selbst. Ein Test, der bei einer kosmetischen Abfrageänderung bricht, ist ein Change-Detector-Anti-Pattern; ein Test, der bricht, wenn sich die garantierte Form oder Berechtigung verschiebt, ist ein Kontrollmechanismus.
§1 – Der GraphQL-Abfragekontrakt
- Jede Operation wird einmal als typisiertes Dokument an einem bekannten Ort verfasst, exportiert und kann importiert werden. Die UI importiert es; der Test importiert exakt dasselbe Artefakt – niemals eine neu getippte Kopie. Das Kopieren einer Abfrage in einen Test tötet den Kontrakt leise (die Bearbeitung der UI-Abfrage bricht den Test nicht mehr). DRY wird lasttragend.
- Es bricht konstruktionsbedingt in beide Richtungen:
- DB → UI-Drift, zur Kompilierzeit.
graphql-codegengeneriert TypeScript-Typen aus dem Live-Schema. Wird ein verwendetes Feld umbenannt oder entfernt, schlägttscbei jeder Verwendung und dem gemeinsamen Dokument fehl – der Build bricht, bevor ein Benutzer ein leeres Panel sieht. Deshalb ist Codegen gegen das Live-Schema zwingend erforderlich. - UI → DB / Berechtigungsdrift, zur Laufzeit. Das gemeinsame Dokument wird gegen eine geseedete Test-DB durch den echten Aufrufpfad unter Rollenkontext ausgeführt und prüft auf die zurückgegebene Form und das Zugriffsergebnis – nicht den Abfragetext.
- DB → UI-Drift, zur Kompilierzeit.
- Kontrakttests und die RLS-Rollenmatrix sind derselbe Rahmen. Führen Sie das eine Dokument als Rolle A (erwarte Daten) und Rolle B (erwarte verweigert/leer) durch PostGraphile aus. Ein Mechanismus beweist drei Dinge: Abfrage entspricht noch Schema, UI-Annahmen gelten noch, RLS erzwingt noch die Grenze.
Ausgearbeitetes Beispiel – wie ein echter Fehler gefangen wird
- DB→UI: Eine Migration entfernt
equipment.is_active. Codegen generiert aus dem neuen Schema; der generierte Typ verliert das Feld;tscschlägt fehl auf dem gemeinsamen Dokument und jeder Komponente, die es verwendet – beim Build gefangen, vor dem Merge. Kein Test prüft „Abfragetext == X." - UI→DB: Ein Entwickler erweitert die UI-Abfrage, um
cancellation_reasonabzurufen (RLS stellt es nur erhöhten Rollen zur Verfügung). Der Laufzeittest, der als einfaches Mitglied läuft, prüft die mitgliedssichtbare Form, erhält nun null/verweigert und schlägt fehl – erzwingt die Frage „Wollten wir das wirklich freigeben?"
§2 – Routenkontrakt- und Smoke-Tests
- Routen aus einer einzigen Quelle aufgezählt. Jede Route hat einen Smoke-Test: rendern ohne Fehler + zeigt einen minimalen, erforderlichen Elementsatz (eine Überschrift, einen Orientierungspunkt, ein zentrales Steuerelement – keine detaillierte Interaktion).
- Hinzufügen/Entfernen einer Route erzwingt eine entsprechende Teständerung – eine neue URL ist ohne ihren Smoke-Test unvollständig; eine entfernte URL entfernt ihren Test. URLs erscheinen oder verschwinden nie still.
- Explizit KEINE detaillierten UI/Interaktionstests – bewusst, umfangbegrenzt. Die Frage ist „Funktioniert diese URL und rendert das Wesentliche", nichts weiter.
Durchsetzung: graphql-codegen + tsc in CI (blockierend) für die Kompilierzeithälfte; Laufzeit-Kontrakt- + Routen-Smoke-Tests in CI gegen eine gesedete DB.
Vollständige Begründung: Artikel XV der Verfassung, gebündelt unter engineering-constitution/references/engineering-constitution.md.