Cypress ist ein automatisierter Test-Runner für browserbasierte Anwendungen und Seiten. Ich verwende es seit Jahren für End-to-End-Tests von Webprojekten und habe mich gefreut, dass Component Testing kürzlich in Cypress Einzug gehalten hat. Ich arbeite an einer großen Enterprise-Vue-Anwendung, und wir verwenden bereits Cypress für End-to-End-Tests. Die meisten unserer Unit- und Component-Tests werden mit Jest und Vue Test Utils geschrieben.
Als Component Testing in Cypress verfügbar wurde, war mein Team sofort dafür, es auszuprobieren und zu aktualisieren. Sie können viel darüber lernen, wie Component Testing funktioniert, direkt in der Cypress-Dokumentation. Daher werde ich einige der Einrichtungsschritte überspringen und mich darauf konzentrieren, wie es ist, mit Component Tests zu arbeiten – wie sehen sie aus, wie verwenden wir sie, und einige Vue-spezifische Fallstricke und Helfer, die wir gefunden haben.
Offenlegung! Zum Zeitpunkt der ersten Entwurfsfassung dieses Artikels war ich der Lead des Frontend-Teams bei einem großen Flottenmanagement-Unternehmen, wo wir Cypress zum Testen verwendeten. Seitdem habe ich bei Cypress angefangen zu arbeiten, wo ich zum Open-Source-Test-Runner beitragen kann.

Alle hier erwähnten Beispiele sind zum Zeitpunkt der Erstellung unter Verwendung von Cypress 8 gültig. Es handelt sich um eine neue Funktion, die sich noch im Alpha-Stadium befindet, und ich wäre nicht überrascht, wenn sich einige dieser Details in zukünftigen Updates ändern.
Wenn Sie bereits Erfahrung mit Tests und Component Tests haben, können Sie direkt zu unseren Erfahrungen im Team springen.
Wie eine Komponententestdatei aussieht
Für ein vereinfachtes Beispiel habe ich ein Projekt erstellt, das eine Komponente für "Datenschutzbestimmungen" enthält. Sie hat einen Titel, einen Text und einen Bestätigungsbutton.

Wenn der Button geklickt wird, wird ein Ereignis ausgelöst, um die übergeordnete Komponente darüber zu informieren, dass dies bestätigt wurde. Hier ist sie auf Netlify bereitgestellt.
Nun hier ist die allgemeine Struktur eines Komponententests in Cypress, der einige der Funktionen verwendet, über die wir sprechen werden.
import { mount } from '@cypress/vue'; // import the vue-test-utils mount function
import PrivacyPolicyNotice from './PrivacyPolicyNotice.vue'; // import the component to test
describe('PrivacyPolicyNotice', () => {
it('renders the title', () => {
// mount the component by itself in the browser 🏗
mount(PrivacyPolicyNotice);
// assert some text is present in the correct heading level 🕵️
cy.contains('h1', 'Privacy Policy').should('be.visible');
});
it('emits a "confirm" event once when confirm button is clicked', () => {
// mount the component by itself in the browser 🏗
mount(PrivacyPolicyNotice);
// this time let's chain some commands together
cy.contains('button', '/^OK/') // find a button element starting with text 'OK' 🕵️
.click() // click the button 🤞
.vue() // use a custom command to go get the vue-test-utils wrapper 🧐
.then((wrapper) => {
// verify the component emitted a confirm event after the click 🤯
expect(wrapper.emitted('confirm')).to.have.length(1)
// `emitted` is a helper from vue-test-utils to simplify accessing
// events that have been emitted
});
});
});
Dieser Test macht einige Aussagen über die **Benutzeroberfläche** und einige über die **Entwicklerschnittstelle** (Respekt an Alex Reviere, der diese Unterscheidung so ausgedrückt hat, dass sie mir einleuchtete). Für die Benutzeroberfläche zielen wir auf bestimmte Elemente mit ihrem erwarteten Textgehalt ab. Für Entwickler testen wir, welche Ereignisse ausgelöst werden. Wir testen auch implizit, dass die Komponente eine korrekt formatierte Vue-Komponente ist; andernfalls würde sie nicht erfolgreich geladen und alle anderen Schritte würden fehlschlagen. Und indem wir **spezifische Arten von Elementen** für bestimmte Zwecke beanspruchen, testen wir die Barrierefreiheit der Komponente – wenn ein zugänglicher `button` jemals zu einem nicht fokussierbaren `div` wird, werden wir es wissen.
So sieht unser Test aus, wenn ich den Button durch ein `div` ersetze. Das hilft uns, das erwartete Tastaturverhalten und die Hinweise für assistierende Technologien, die ein Button-Element kostenlos mit sich bringt, beizubehalten, indem es uns benachrichtigt, wenn wir es versehentlich austauschen.

Ein wenig Grundlagenarbeit
Nachdem wir nun gesehen haben, wie ein Komponententest aussieht, gehen wir ein wenig zurück und sprechen darüber, wie dies in unsere Gesamtstrategie für Tests passt. Es gibt viele Definitionen für diese Dinge, also ganz kurz, für mich, in unserem Codebestand:
- Unit-Tests bestätigen, dass einzelne Funktionen wie erwartet funktionieren, wenn sie von einem Entwickler verwendet werden.
- Komponententests laden einzelne UI-Komponenten isoliert und bestätigen, dass sie wie erwartet funktionieren, wenn sie von einem Endbenutzer und einem Entwickler verwendet werden.
- End-to-End-Tests besuchen die Anwendung, führen Aktionen aus und bestätigen, dass die gesamte App wie erwartet funktioniert, wenn sie nur von einem Endbenutzer verwendet wird.
Schließlich ist Integrationstesting für mich ein etwas schwammigerer Begriff und kann auf jeder Ebene stattfinden – eine Einheit, die andere Funktionen importiert, eine Komponente, die andere Komponenten importiert, oder sogar ein "End-to-End"-Test, der API-Antworten mockt und nicht auf die Datenbank zugreift, könnten alle als Integrationstests betrachtet werden. Sie testen mehr als einen Teil einer Anwendung, der zusammenarbeitet, aber nicht das Ganze. Ich bin mir nicht sicher über den wirklichen Nutzen dieser Kategorie, da sie sehr breit gefächert zu sein scheint, aber verschiedene Leute und Organisationen verwenden diese Begriffe auf andere Weise, daher wollte ich sie erwähnen.
Für einen längeren Überblick über die verschiedenen Arten von Tests und ihre Beziehung zur Frontend-Arbeit können Sie "Front-End Testing is For Everyone" von Evgeny Klimenchenko lesen: „Front-End Testing is For Everyone“.
Komponententests
In den obigen Definitionen werden die verschiedenen Testebenen danach definiert, wer einen Code verwendet und wie der Vertrag mit dieser Person aussieht. Für mich als Entwickler sollte eine Funktion, die die Zeit formatiert, immer das korrekte Ergebnis liefern, wenn ich ihr ein gültiges Date-Objekt übergebe, und klare Fehler auslösen, wenn ich etwas anderes übergebe. Dies sind Dinge, die wir testen können, indem wir die Funktion isoliert aufrufen und verifizieren, dass sie auf verschiedene Bedingungen korrekt reagiert, unabhängig von der Benutzeroberfläche. Die "Entwicklerschnittstelle" (oder API) einer Funktion dreht sich alles darum, dass Code mit anderem Code spricht.
Betrachten wir nun die Komponententests. Der "Vertrag" einer Komponente besteht eigentlich aus zwei Verträgen:
- Für den Entwickler, der eine Komponente verwendet, verhält sich die Komponente korrekt, wenn die **erwarteten Ereignisse basierend auf Benutzereingaben** oder anderen Aktivitäten ausgelöst werden. Es ist auch fair, Dinge wie Prop-Typen und Validierungsregeln in unsere Vorstellung von "korrektem, entwicklerorientiertem Verhalten" einzubeziehen, obwohl diese Dinge auch auf Unit-Ebene getestet werden können. Was ich als Entwickler wirklich von einem Komponententest erwarte, ist zu wissen, dass er geladen wird und die Signale aussendet, die er aufgrund von Interaktionen senden soll.
- Für den Benutzer, der mit einer Komponente interagiert, verhält sich diese korrekt, wenn die **UI den Zustand der Komponente** jederzeit widerspiegelt. Dies beinhaltet mehr als nur den visuellen Aspekt. Das von der Komponente generierte HTML ist die Grundlage für ihren Barrierefreiheitsbaum, und der Barrierefreiheitsbaum bietet die API für Tools wie Screenreader, um den Inhalt korrekt anzukündigen. Daher ist die Komponente für mich nicht "korrekt verhalten", wenn sie nicht das korrekte HTML für den Inhalt rendert.
An diesem Punkt ist klar, dass Component Testing zwei Arten von Assertions erfordert – manchmal überprüfen wir Vue-spezifische Dinge, wie z. B. "wie viele Ereignisse eines bestimmten Typs wurden ausgelöst?", und manchmal überprüfen wir benutzerorientierte Dinge, wie z. B. "wurde tatsächlich eine sichtbare Erfolgsmeldung auf dem Bildschirm angezeigt?".
Es scheint auch, dass Komponententests ein mächtiges Dokumentationswerkzeug sind. Die Tests sollten alle kritischen Funktionen einer Komponente beanspruchen – die definierten Verhaltensweisen, von denen ab.hängt – und Details ignorieren, die nicht kritisch sind. Das bedeutet, dass wir uns die Tests ansehen können, um zu verstehen (oder uns in sechs Monaten oder einem Jahr daran zu erinnern!), wie das erwartete Verhalten einer Komponente aussieht. Und, wenn alles gut geht, können wir jede Funktion ändern, die nicht explizit vom Test beansprucht wird, ohne den Test neu schreiben zu müssen. Designänderungen, Animationen, Verbesserung des DOM – all das sollte möglich sein, und wenn ein Test fehlschlägt, dann aus einem Grund, der Sie betrifft, nicht weil ein Element von einem Teil des Bildschirms an einen anderen verschoben wurde.
Dieser letzte Punkt erfordert Sorgfalt bei der Gestaltung von Tests und insbesondere bei der Auswahl von Selektoren für die zu interagierenden Elemente, sodass wir später auf dieses Thema zurückkommen werden.
Wie Vue-Komponententests mit und ohne Cypress funktionieren
Auf hoher Ebene hat sich eine Kombination aus Jest und der Vue Test Utils-Bibliothek mehr oder weniger zum Standardansatz für die Ausführung von Komponententests entwickelt, den ich bisher gesehen habe.
Vue Test Utils bietet uns Hilfsmittel zum Laden einer Komponente, zum Konfigurieren ihrer Optionen und zum Mocken verschiedener Dinge, von denen eine Komponente abhängen könnte, um ordnungsgemäß zu funktionieren. Es stellt auch ein wrapper-Objekt um die geladene Komponente bereit, um es einfacher zu machen, Aussagen darüber zu treffen, was mit der Komponente passiert.
Jest ist ein großartiger Test-Runner und stellt die geladene Komponente mithilfe von jsdom zum Simulieren einer Browserumgebung bereit.
Der Component-Test-Runner von Cypress selbst verwendet Vue Test Utils zum Laden von Vue-Komponenten. Der Hauptunterschied zwischen den beiden Ansätzen ist also der Kontext. Cypress führt bereits End-to-End-Tests im Browser aus, und Komponententests funktionieren genauso. Das bedeutet, wir können sehen, wie unsere Tests ausgeführt werden, sie mitten im Test anhalten, mit der App interagieren oder Dinge untersuchen, die früher im Lauf passiert sind, und wissen, dass Browser-APIs, von denen unsere Anwendung abhängt, echtes Browserverhalten sind und keine jsdom-gemockten Versionen dieser Funktionen.
Sobald die Komponente geladen ist, gelten alle üblichen Cypress-Funktionen, die wir in End-to-End-Tests verwendet haben, und einige Schwierigkeiten bei der Auswahl von Elementen entfallen. Hauptsächlich wird Cypress die Simulation aller Benutzerinteraktionen und die Erstellung von Aussagen über die Reaktion der Anwendung auf diese Interaktionen übernehmen. Dies deckt den benutzerorientierten Teil des Komponentenkontrakts vollständig ab, aber was ist mit den entwicklerorientierten Dingen, wie Ereignissen, Props und allem anderen? Hier kommt Vue Test Utils wieder ins Spiel. Innerhalb von Cypress können wir auf den Wrapper zugreifen, den Vue Test Utils um die geladene Komponente erstellt, und Aussagen darüber treffen.
Was mir daran gefällt ist, dass wir Cypress und Vue Test Utils beide für das nutzen, was sie wirklich gut können. Wir können das Verhalten der Komponente als Benutzer ohne jeglichen Framework-spezifischen Code testen und nur dann auf Vue Test Utils zurückgreifen, um die Komponente zu laden und spezifisches Framework-Verhalten zu überprüfen, wenn wir uns dafür entscheiden. Wir müssen niemals auf ein Vue-spezifisches $nextTick awaiten, nachdem wir etwas Vue-spezifisches getan haben, um den Zustand einer Komponente zu aktualisieren. Das war immer das kniffligste, was man neuen Entwicklern ohne Vue-Erfahrung erklären musste – wann und warum sie Dinge awaiten müssen, wenn sie einen Test für eine Vue-Komponente schreiben.
Unsere Erfahrungen mit Komponententests
Die Vorteile des Component Testings klangen für uns großartig, aber natürlich können in einem großen Projekt nur wenige Dinge out-of-the-box reibungslos funktionieren, und als wir mit unseren Tests begannen, stießen wir auf einige Probleme. Wir betreiben eine große Enterprise-SPA, die mit Vue 2 und der Vuetify-Komponentenbibliothek erstellt wurde. Die meiste unserer Arbeit nutzt stark die eingebauten Komponenten und Stile von Vuetify. Während also der Ansatz "Komponenten isoliert testen" nett klingt, war eine wichtige Lektion, die wir gelernt haben, dass wir einen Kontext für das Laden unserer Komponenten einrichten mussten, und wir Vuetify und einige globale Stile benötigten, sonst würde nichts funktionieren.
Cypress hat einen Discord-Server, auf dem Leute Hilfe suchen können, und als ich feststeckte, stellte ich dort Fragen. Leute aus der Community – sowie Cypress-Teammitglieder – leiteten mich freundlicherweise zu Beispiel-Repos, Code-Schnipseln und Ideen zur Lösung unserer Probleme. Hier ist eine Liste der kleinen Dinge, die wir verstehen mussten, um unsere Komponenten korrekt zu laden, aufgetretene Fehler und was sonst noch interessant oder hilfreich erscheint:
- Importieren und Verwenden von Vuetify
- Hinzufügen einiger Attribute, die Vuetify benötigt, zum
__cy_root-Element - Verwenden von
.spec-Dateien neben den Komponentendateien, nicht in einem separaten Testordner - Umgang mit einem Konflikt beim Import von benutzerdefinierten Befehlen zwischen Component Tests und End-to-End-Tests
- Einfacher Zugriff auf den Vue-Wrapper im Cypress-Kontext
Importieren von Vuetify
Durch Beobachtung im Cypress Discord hatte ich dieses Beispiel-Repo für Component Testing mit Vuetify von Bart Ledoux gesehen, also war das mein Ausgangspunkt. Dieses Repo organisiert den Code in einem recht üblichen Muster, das einen plugins-Ordner enthält, wo ein Plugin eine Instanz von Vuetify exportiert. Dies wird von der Anwendung selbst importiert, kann aber auch von unserer Testeinrichtung importiert und beim Laden der zu testenden Komponente verwendet werden. Im Repo wird ein Befehl zu Cypress hinzugefügt, der die Standardfunktion mount durch eine ersetzt, die eine Komponente mit Vuetify lädt.
Hier ist der gesamte Code, der dafür benötigt wird, vorausgesetzt, wir haben alles in commands.js gemacht und nichts aus dem plugins-Ordner importiert. Wir machen das mit einem benutzerdefinierten Befehl, was bedeutet, dass wir anstatt die Vue Test Utils mount-Funktion direkt in unseren Tests aufzurufen, unseren eigenen cy.mount-Befehl aufrufen werden.
// the Cypress mount function, which wraps the vue-test-utils mount function
import { mount } from "@cypress/vue";
import Vue from 'vue';
import Vuetify from 'vuetify/lib/framework';
Vue.use(Vuetify);
// add a new command with the name "mount" to run the Vue Test Utils
// mount and add Vuetify
Cypress.Commands.add("mount", (MountedComponent, options) => {
return mount(MountedComponent, {
vuetify: new Vuetify({});, // the new Vuetify instance
...options, // To override/add Vue options for specific tests
});
});
Jetzt haben wir immer Vuetify zusammen mit unseren Komponenten, wenn sie geladen werden, und wir können trotzdem alle anderen Optionen übergeben, die wir für die jeweilige Komponente benötigen. Aber wir müssen Vuetify nicht jedes Mal manuell hinzufügen.
Hinzufügen von Attributen, die Vuetify benötigt
Das einzige Problem mit dem neuen mount-Befehl oben ist, dass Vuetify-Komponenten für eine korrekte Funktion in einem bestimmten DOM-Kontext gerendert werden müssen. Apps, die Vuetify verwenden, umschließen alles in einer <v-app>-Komponente, die das Wurzelelement der Anwendung darstellt. Es gibt ein paar Möglichkeiten, dies zu handhaben, aber die einfachste ist, dem Befehl selbst etwas Setup hinzuzufügen, bevor er eine Komponente lädt.
Cypress.Commands.add("mount", (MountedComponent, options) => {
// get the element that our mounted component will be injected into
const root = document.getElementById("__cy_root");
// add the v-application class that allows Vuetify styles to work
if (!root.classList.contains("v-application")) {
root.classList.add("v-application");
}
// add the data-attribute — Vuetify selector used for popup elements to attach to the DOM
root.setAttribute('data-app', 'true');
return mount(MountedComponent, {
vuetify: new Vuetify({}),
...options,
});
});
Dies nutzt die Tatsache aus, dass Cypress selbst ein Wurzelelement erstellen muss, um unsere Komponente tatsächlich zu laden. Dieses Wurzelelement ist das Elternteil unserer Komponente und hat die ID __cy_root. Dies gibt uns einen Ort, um einfach die richtigen Klassen und Attribute hinzuzufügen, die Vuetify erwartet zu finden. Nun werden Komponenten, die Vuetify-Komponenten verwenden, korrekt aussehen und funktionieren.
Eine weitere Sache, die wir nach einiger Testarbeit bemerkt haben, ist, dass die erforderliche Klasse v-application eine display-Eigenschaft von flex hat. Das macht Sinn in einem vollständigen App-Kontext, der Vuetifys Container-System verwendet, hatte aber einige unerwünschte visuelle Nebeneffekte für uns beim Laden einzelner Komponenten – also fügten wir eine weitere Zeile hinzu, um diesen Stil zu überschreiben, bevor die Komponente geladen wird.
root.setAttribute('style', 'display: block');
Dies beseitigte die gelegentlichen Layout-Probleme, und dann waren wir wirklich fertig mit der Anpassung des umgebenden Kontexts für das Laden von Komponenten.
Spec-Dateien dort platzieren, wo wir sie haben wollen
Viele der Beispiele zeigen eine cypress.json-Konfigurationsdatei, wie diese hier für Component Testing:
{
"fixturesFolder": false,
"componentFolder": "src/components",
"testFiles": "**/*.spec.js"
}
Das ist eigentlich ziemlich nah an dem, was wir wollen, da die Eigenschaft testFiles ein Glob-Muster akzeptiert. Dieses hier besagt: "Suche in jedem Ordner nach Dateien, die mit .spec.js enden." In unserem Fall, und wahrscheinlich in vielen anderen, enthielt der node_modules-Ordner des Projekts einige irrelevante spec.js-Dateien, die wir durch Präfixieren von !(node_modules) wie folgt ausgeschlossen haben:
"testFiles": "!(node_modules)**/*.spec.js"
Bevor wir uns auf diese Lösung einigten und während wir experimentierten, hatten wir dies auf einen bestimmten Ordner gesetzt, in dem Komponententests leben würden, nicht auf ein Glob-Muster, das sie überall finden könnte. Unsere Tests leben direkt neben unseren Komponenten, also hätte das in Ordnung sein können, aber wir haben tatsächlich zwei unabhängige components-Ordner, da wir einen kleinen Teil unserer App verpacken und veröffentlichen, der in anderen Projekten im Unternehmen verwendet wird. Nachdem ich diese Änderung früh vorgenommen hatte, gebe ich zu, dass ich sicherlich vergessen hatte, dass es ursprünglich ein Glob war, und fing an, vom Kurs abzukommen, bevor ich in den Discord wechselte, wo ich eine Erinnerung erhielt und es herausfand. Ein Ort zu haben, an dem man schnell prüfen kann, ob etwas der richtige Ansatz ist, war viele Male hilfreich.
Konflikt bei Befehlsdateien
Das Befolgen des oben skizzierten Musters, um Vuetify mit unseren Komponententests zum Laufen zu bringen, führte zu einem Problem. Wir hatten all diese Dinge in dieselbe commands.js-Datei gepackt, die wir für reguläre End-to-End-Tests verwendeten. Während wir also ein paar Komponententests zum Laufen brachten, starteten unsere End-to-End-Tests nicht einmal. Es gab einen frühen Fehler bei einem der Importe, der nur für Component Testing benötigt wurde.
Mir wurden ein paar Lösungen empfohlen, aber an dem Tag entschied ich mich, den Ladebefehl und seine Abhängigkeiten in eine eigene Datei zu extrahieren und ihn nur dort zu importieren, wo er in den Komponententests selbst benötigt wurde. Da dies die einzige Quelle für Probleme beim Ausführen beider Testarten war, war es eine saubere Möglichkeit, dies aus dem End-to-End-Kontext zu entfernen, und es funktioniert als eigenständige Funktion einwandfrei. Wenn wir weitere Probleme haben oder wenn wir das nächste Mal aufräumen, würden wir wahrscheinlich der Hauptempfehlung folgen, zwei separate Befehlsdateien zu haben und die gemeinsamen Teile zwischen ihnen zu teilen.
Zugriff auf den Vue Test Utils Wrapper
Im Kontext eines Komponententests ist der Vue Test Utils-Wrapper unter Cypress.vueWrapper verfügbar. Beim Zugriff darauf, um Aussagen zu treffen, hilft es, cy.wrap zu verwenden, um das Ergebnis wie andere Befehle, auf die über cy zugegriffen wird, verkettbar zu machen. Jessica Sachs fügt in ihrem Beispiel-Repo einen kurzen Befehl hinzu, um dies zu tun. Also habe ich, wieder in commands.js, Folgendes hinzugefügt:
Cypress.Commands.add('vue', () => {
return cy.wrap(Cypress.vueWrapper);
});
Dies kann in einem Test wie folgt verwendet werden:
mount(SomeComponent)
.contains('button', 'Do the thing once')
.click()
.should('be.disabled')
.vue()
.then((wrapper) => {
// the Vue Test Utils `wrapper` has an API specifically setup for testing:
// https://vue-test-utils.vuejs.org/api/wrapper/#properties
expect(wrapper.emitted('the-thing')).to.have.length(1);
});
Dies liest sich für mich sehr natürlich und trennt klar, wann wir mit der Benutzeroberfläche arbeiten, im Vergleich dazu, wann wir Details inspizieren, die über den Vue Test Utils-Wrapper aufgedeckt werden. Es betont auch, dass, wie bei vielen Dingen in Cypress, um das Beste daraus zu machen, es wichtig ist, die Werkzeuge zu verstehen, die es nutzt, und nicht nur Cypress selbst. Cypress verwendet Mocha, Chai und verschiedene andere Bibliotheken. In diesem Fall ist es nützlich zu verstehen, dass Vue Test Utils eine externe Open-Source-Lösung mit einer eigenen vollständigen Dokumentation ist, und dass wir uns innerhalb des then-Callbacks oben in der Vue Test Utils-Welt befinden – nicht in der Cypress-Welt – damit wir uns für Hilfe und Dokumentation an den richtigen Ort wenden.
Herausforderungen
Da dies eine kürzliche Erkundung war, haben wir die Cypress-Komponententests noch nicht in unsere CI/CD-Pipelines aufgenommen. Fehler blockieren keine Pull-Anfragen, und wir haben uns nicht um das Hinzufügen von Berichten für diese Tests gekümmert. Ich erwarte dort keine Überraschungen, aber es ist erwähnenswert, dass wir diese noch nicht vollständig in unseren gesamten Workflow integriert haben. Ich kann nicht spezifisch darauf eingehen.
Es ist auch relativ früh für den Component-Test-Runner, und es gibt ein paar Stolpersteine. Anfangs schien es, als ob jeder zweite Testlauf einen Linter-Fehler anzeigte und manuell neu geladen werden musste. Ich bin dem nicht auf den Grund gegangen, und dann hat es sich selbst behoben (oder wurde durch eine neuere Cypress-Version behoben). Ich würde erwarten, dass ein neues Werkzeug potenzielle Probleme wie diese hat.
Ein weiterer Stolperstein beim Component Testing im Allgemeinen ist, dass es je nach Funktionsweise Ihrer Komponente schwierig sein kann, sie ohne viel Aufwand zu laden, indem Sie andere Teile Ihres Systems mocken. Wenn die Komponente mit mehreren Vuex-Modulen interagiert oder API-Aufrufe zum Abrufen eigener Daten verwendet, müssen Sie all das simulieren, wenn Sie die Komponente laden. Während End-to-End-Tests fast absurd einfach einzurichten sind für jedes Projekt, das im Browser läuft, sind Komponententests für bestehende Komponenten viel empfindlicher für Ihr Komponentendesign.
Dies gilt für alles, das Komponenten isoliert lädt, wie Storybook und Jest, die wir ebenfalls verwendet haben. Oftmals, wenn Sie versuchen, Komponenten isoliert zu laden, stellen Sie fest, wie viele Abhängigkeiten Ihre Komponenten tatsächlich haben, und es scheint, als ob viel Aufwand erforderlich ist, nur um den richtigen Kontext für das Laden bereitzustellen. Dies bewegt uns langfristig zu einem besseren Komponentendesign, mit Komponenten, die einfacher zu testen sind und dabei weniger Teile des Codebestands berühren.
Aus diesem Grund würde ich vorschlagen, wenn Sie noch keine Komponententests haben und daher nicht sicher sind, was Sie mocken müssen, um Ihre Komponente zu laden, wählen Sie Ihre ersten Komponententests sorgfältig aus, um die Anzahl der Faktoren zu begrenzen, die Sie richtig machen müssen, bevor Sie die Komponente im Test-Runner sehen können. Wählen Sie eine kleine, präsentationsorientierte Komponente, die Inhalte über Props oder Slots rendert, um sie in Aktion zu sehen, bevor Sie sich in die Details der Abhängigkeiten vertiefen.
Vorteile
Der Component-Test-Runner hat für unser Team gut funktioniert. Wir haben bereits umfangreiche End-to-End-Tests in Cypress, so dass das Team mit der Erstellung neuer Tests und dem Schreiben von Benutzerinteraktionen vertraut ist. Und wir verwenden auch Vue Test Utils für einzelne Komponententests. Daher gab es hier nicht wirklich viel Neues zu lernen. Die anfänglichen Einrichtungsprobleme hätten frustrierend sein können, aber es gibt viele freundliche Leute, die helfen können, Probleme zu lösen, daher bin ich froh, dass ich die Superkraft "Hilfe suchen" genutzt habe.
Ich würde sagen, es gibt zwei Hauptvorteile, die wir gefunden haben. Einer ist der konsistente Ansatz für den Testcode selbst zwischen den Testebenen. Dies hilft, da es keine mentale Umstellung mehr gibt, um über subtile Unterschiede zwischen Jest- und Cypress-Interaktionen, Browser-DOM vs. jsdom und ähnliche Probleme nachzudenken.
Der andere ist die Möglichkeit, Komponenten isoliert zu entwickeln und dabei visuelles Feedback zu erhalten. Durch die Einrichtung aller Variationen einer Komponente für Entwicklungszwecke erhalten wir die Gliederung des UI-Tests fertig und vielleicht auch ein paar Assertions. Es fühlt sich an, als ob wir von Anfang an mehr Wert aus dem Testprozess ziehen, sodass es weniger wie eine nachträglich angefügte Aufgabe am Ende eines Tickets ist.
Dieser Prozess ist für uns nicht ganz testgesteuerte Entwicklung, obwohl wir dazu übergehen können, aber er ist oft "demo-gesteuert" in dem Sinne, dass wir die Zustände eines neuen UI-Teils präsentieren wollen, und Cypress ist ein ziemlich guter Weg, dies zu tun, indem cy.pause() verwendet wird, um einen laufenden Test nach bestimmten Interaktionen anzuhalten und über den Zustand der Komponente zu sprechen. Die Entwicklung mit diesem Gedanken im Hinterkopf, wissend, dass wir die Tests verwenden werden, um die Features der Komponente in einer Demo zu durchlaufen, hilft, die Tests sinnvoll zu organisieren und ermutigt uns, alle Szenarien abzudecken, die uns zur Entwicklungszeit einfallen, anstatt danach.
Fazit
Das mentale Modell dafür, was genau Cypress als Ganzes tut, war für mich schwierig zu verstehen, als ich es zum ersten Mal kennenlernte, da es so viele andere Open-Source-Tools im Test-Ökosystem kapselt. Man kann schnell mit Cypress loslegen, ohne ein tiefes Wissen darüber zu haben, welche anderen Tools im Hintergrund verwendet werden.
Das bedeutete, dass ich, wenn etwas schiefging, nicht sicher war, welche Ebene ich bedenken sollte – funktionierte etwas nicht wegen eines Mocha-Problems? Ein Chai-Problem? Ein schlechter jQuery-Selektor in meinem Testcode? Falsche Verwendung eines Sinon-Spys? Irgendwann musste ich mich zurückziehen und etwas über diese einzelnen Puzzleteile lernen und welche genaue Rolle sie in meinen Tests spielten.
Dies ist bei Component Testing immer noch der Fall, und jetzt gibt es eine zusätzliche Ebene: Framework-spezifische Bibliotheken zum Laden und Testen von Komponenten. In gewisser Weise ist das mehr Aufwand und mehr zu lernen. Andererseits integriert Cypress diese Tools auf kohärente Weise und verwaltet deren Einrichtung, sodass wir eine völlig separate Testeinrichtung für Component Tests vermeiden können. Für uns war es wichtig, Komponenten isoliert zu laden, um sie mit Jest zu testen und in Storybook zu verwenden, daher haben wir viele der notwendigen Mocking-Ideen im Voraus erarbeitet und aus diesem Grund gut getrennte Komponenten mit einfachen Prop/Event-basierten Schnittstellen bevorzugt.
Alles in allem arbeiten wir gerne mit dem Test-Runner, und ich habe das Gefühl, dass ich mehr Tests (und lesbareren Testcode!) in Pull-Anfragen sehe, die ich überprüfe, also ist das für mich ein Zeichen dafür, dass wir uns in eine gute Richtung bewegt haben.
Glaubst du nicht, dass E2E-Tests unabhängig von der Implementierung sein sollten? Vue, React, Angular rendern am Ende des Tages DOM, und das ist genau das, was der Benutzer sieht. Dieser Ansatz mag auf den ersten Blick mächtig erscheinen, da man nützliche Hilfsmittel nutzen kann, aber die Tests werden durch das Framework/die Bibliothek deiner Wahl eingeschränkt.
Ich stimme zu, dass E2E-Tests unabhängig von der Implementierung sein sollten. Ich schlage nicht vor, E2E-Tests durch Komponententests zu ersetzen, sie ergänzen sich gegenseitig. Abgesehen davon sollten Komponententests, die sich auf die Benutzeroberfläche konzentrieren, ebenfalls unabhängig von der Implementierung sein und die DOM-Ausgabe der Komponente so testen, wie es ein Benutzer tun würde. Wenn die Komponente von Vue zu React neu geschrieben würde, würden sich die DOM-Assertionen nicht ändern, sondern nur der Befehl zum Mounten der Komponente. Abgesehen vom Mounten einer Komponente denke ich, dass wir nur dann auf Framework-spezifische Hilfsmittel zurückgreifen sollten, wenn wir explizit etwas testen wollen, das eng mit dem Framework verbunden ist, wie z. B. welche Vue-Events emittiert werden.
Ich versuche, Ihren Vorschlag für die Unterstützung von Vue3, Vite und Vuetify 3 (Beta) zu adaptieren.
Aber bisher ohne Erfolg.
Dies sind die Schritte, die mich zu dem Moment geführt haben, in dem ich einen spezifischen Cypress-Mount-Befehl für Komponententests erstellen möchte.
Ich denke, ich bin nicht weit davon entfernt, herauszufinden, wie es funktionieren könnte (oder ob es nicht machbar ist).
https://github.com/baldir-fr/starter-vue3-vuetify3-vite-cypress-component-e2e/blob/main/making-of.md
Der Markdown-Artikel befindet sich im Quellcode-Repository (https://github.com/baldir-fr/starter-vue3-vuetify3-vite-cypress-component-e2e).