Wann Kindkomponenten gestubbt werden sollten und wann nicht
shallowMount ersetzt jede Kindkomponente durch einen Stub und rendert nur die getestete Komponente selbst, mount baut den kompletten Komponentenbaum auf. Beide Funktionen aus Vue Test Utils lösen unterschiedliche Testprobleme, und wer sie verwechselt, bekommt entweder Tests, die zu viel gleichzeitig prüfen, oder Tests, die an internen Details von Kindkomponenten zerbrechen.
Inhaltsverzeichnis
- 1. Was shallowMount und mount unterschiedlich machen
- 2. Wann sich shallowMount anbietet
- 3. Wann mount die richtige Wahl ist
- 4. Fallstricke bei Slots
- 5. Fallstricke bei Provide/Inject
- 6. Praktisches Beispiel: beide Ansätze im selben Testfile
- 7. Feingranulare Kontrolle über Stubs
- 8. Performance bei tiefen Komponentenbäumen
- 9. Entscheidungshilfe für die Praxis
- 10. Zusammenfassung
- 11. FAQ
1. Was shallowMount und mount unterschiedlich machen
Beide Funktionen aus @vue/test-utils rendern eine Vue-Komponente in einer isolierten Testumgebung und geben einen Wrapper zurück, über den sich der gerenderte Baum inspizieren, Events auslösen und Props ändern lassen. Der entscheidende Unterschied liegt darin, was mit Kindkomponenten passiert. mount() rendert jede Kindkomponente vollständig, inklusive ihrer eigenen setup()-Logik, Lifecycle-Hooks und aller weiteren Verschachtelungen. shallowMount() dagegen ersetzt jede Kindkomponente automatisch durch einen einfachen Stub, der nur den Komponentennamen als Platzhalter-Tag rendert, ohne die eigentliche Implementierung auszuführen.
Das klingt nach einer rein technischen Feinheit, hat aber direkte Konsequenzen für das, was ein Test eigentlich aussagt. Ein Test mit mount() prüft implizit auch das Verhalten aller Kindkomponenten mit, weil sie tatsächlich ausgeführt werden. Ein Fehler in einer tief verschachtelten Kindkomponente kann dadurch einen Test zum Scheitern bringen, der eigentlich nur die Elternkomponente betrifft. shallowMount() kappt diese Kette bewusst und macht den Test unempfindlich gegenüber allem, was innerhalb der Kindkomponenten passiert, solange nur deren öffentliche Schnittstelle über Props und Events stimmt.
import { describe, it, expect } from 'vitest'
import { shallowMount, mount } from '@vue/test-utils'
import ProductCard from '@/components/ProductCard.vue'
describe('ProductCard', () => {
it('rendert Kindkomponenten nur als Stub (isoliert)', () => {
const wrapper = shallowMount(ProductCard, {
props: { name: 'Teekanne', price: 29.9 },
})
// PriceTag wird nicht wirklich ausgeführt, nur als Stub-Tag gerendert
expect(wrapper.findComponent({ name: 'PriceTag' }).exists()).toBe(true)
expect(wrapper.text()).toContain('Teekanne')
})
it('rendert den vollen Baum inkl. PriceTag-Logik', () => {
const wrapper = mount(ProductCard, {
props: { name: 'Teekanne', price: 29.9 },
})
// Hier wird die tatsächliche Formatierung aus PriceTag geprüft
expect(wrapper.text()).toContain('29,90 €')
})
})
2. Wann sich shallowMount anbietet
shallowMount passt am besten zu klassischen Unit-Tests, bei denen genau eine Komponente im Fokus steht und ihre Kindkomponenten austauschbare Implementierungsdetails sind. Typische Kandidaten sind Container- oder Layout-Komponenten, die viele Kindkomponenten orchestrieren, aber selbst wenig eigene Logik enthalten außer Props durchzureichen und auf Events zu reagieren. Wird hier mit mount getestet, hängt der Test plötzlich von der korrekten Funktion aller Kinder ab, was Fehlerursachen verschleiert, sobald ein Test rot wird.
Ein weiterer Vorteil zeigt sich bei tiefen Komponentenbäumen: Jede zusätzliche Ebene, die mount tatsächlich rendert, kostet Zeit, insbesondere wenn Kindkomponenten selbst wieder asynchrone Daten laden, Timer starten oder aufwendige Berechnungen im setup() durchführen. Bei einer Testsuite mit mehreren hundert Komponententests summiert sich dieser Unterschied schnell zu spürbar längeren CI-Laufzeiten. shallowMount hält die Ausführungszeit pro Test konstant niedrig, weil immer nur eine Komponentenebene tatsächlich läuft, unabhängig davon, wie tief der reale Baum in Produktion ist.
3. Wann mount die richtige Wahl ist
mount ist unverzichtbar, sobald ein Test das tatsächliche Zusammenspiel mehrerer Komponenten prüfen soll, etwa ob ein Klick in einer Kindkomponente korrekt bis zur Elternkomponente durchgereicht wird und dort eine sichtbare Änderung auslöst. Integrationsartige Tests, die einen kompletten Formular-Flow über mehrere Feldkomponenten hinweg abdecken, ergeben mit gestubbten Kindern wenig Sinn, weil genau die Interaktion zwischen den Teilen der eigentliche Testgegenstand ist.
Auch bei kleinen, rein präsentationsorientierten Komponenten ohne relevante Kindkomponenten macht mount oft den einfacheren Eindruck, weil kein zusätzlicher Gedanke an Stubbing-Strategien nötig ist. Als Faustregel hat sich bewährt: Je näher ein Test an einem echten End-to-End-Szenario innerhalb der Komponentenwelt liegt, desto eher lohnt sich mount. Je isolierter eine einzelne Verantwortlichkeit geprüft werden soll, desto eher gewinnt shallowMount.
4. Fallstricke bei Slots
Ein häufiger Stolperstein bei shallowMount betrifft Slots. Wird eine Kindkomponente gestubbt, rendert Vue Test Utils standardmäßig keinen Slot-Inhalt, den diese Kindkomponente eigentlich an ihrer Stelle einfügen würde, weil der Stub die interne <slot />-Definition nicht kennt. Ein Test, der prüft, ob bestimmter Text über einen Default-Slot in einer Kindkomponente sichtbar wird, schlägt mit shallowMount unter Umständen fehl, obwohl die Komponente in Produktion korrekt funktioniert.
Die Option shallow: false für einzelne Kindkomponenten oder ein gezieltes stubs-Objekt mit { template: '<slot />' } für die betroffene Kindkomponente löst dieses Problem gezielt, ohne gleich auf vollständiges mount umzusteigen. Wichtig ist, sich bewusst zu machen, dass Slot-Verhalten kein Implementierungsdetail einer Kindkomponente ist, sondern Teil ihres öffentlichen Vertrags, und deshalb in Tests, die genau das prüfen wollen, entsprechend konfiguriert werden muss.
5. Fallstricke bei Provide/Inject
Provide/Inject-Ketten sorgen bei shallowMount für ein zweites, subtileres Problem. Da Kindkomponenten gar nicht wirklich ausgeführt werden, wird auch nicht geprüft, ob ein injizierter Wert dort korrekt ankommt und verarbeitet wird. Ein Fehler in der Injection-Kette, etwa ein falscher Injection-Key oder ein fehlender Default-Wert, bleibt mit shallowMount unsichtbar, weil der Stub den inject()-Aufruf der echten Kindkomponente nie erreicht.
Wer Provide/Inject als zentralen Kommunikationsweg zwischen Eltern- und Kindkomponenten nutzt, etwa für ein Formular-Kontext-Objekt, sollte diesen Pfad gezielt mit mount oder zumindest einem gezielt nicht gestubbten Kind testen. Eine sinnvolle Strategie ist, den reinen Aufbau und die Props-Weitergabe mit shallowMount abzudecken und die tatsächliche Provide/Inject-Kommunikation in einem separaten, kleineren Integrationstest mit mount zu verifizieren, statt beides in einem einzigen, überladenen Test zu vermischen.
6. Praktisches Beispiel: beide Ansätze im selben Testfile
In der Praxis schließen sich shallowMount und mount nicht aus, sondern ergänzen sich innerhalb derselben Testdatei je nach Testfall. Für eine Formularkomponente mit mehreren Feld-Unterkomponenten lässt sich mit shallowMount schnell prüfen, ob alle erwarteten Felder überhaupt gerendert werden und ob Props korrekt durchgereicht werden. Für den eigentlichen Absende-Flow, bei dem ein Klick in einem Feld letztlich den Submit-Handler der Elternkomponente mit den richtigen Werten aufrufen soll, ist mount die passendere Wahl, weil hier die reale Interaktion zwischen den Komponenten getestet wird.
Diese Kombination hält die Testsuite insgesamt schnell, weil die Mehrzahl der Tests, die reine Struktur- und Props-Fragen klären, mit dem günstigeren shallowMount auskommt, während die wenigen wirklich interaktionskritischen Tests bewusst den teureren, aber aussagekräftigeren mount-Weg gehen. Wer diese Unterscheidung von Anfang an bewusst trifft, vermeidet sowohl overengineerte Stub-Konfigurationen als auch unnötig langsame Testsuiten.
7. Feingranulare Kontrolle über Stubs
Vue Test Utils erlaubt es, das Alles-oder-nichts-Verhalten von shallowMount gezielt zu durchbrechen. Über die Option global.stubs lässt sich für einzelne Kindkomponenten explizit festlegen, ob sie gestubbt werden sollen oder nicht, etwa stubs: { PriceTag: false }, um genau diese eine Komponente trotz shallowMount vollständig zu rendern. Umgekehrt lässt sich bei mount mit stubs: { HeavyChart: true } gezielt eine einzelne, besonders teure Kindkomponente ausklammern, ohne gleich auf shallowMount für die gesamte Testdatei umzusteigen.
Diese Feinsteuerung ist besonders bei Komponenten wertvoll, die eine einzelne schwergewichtige Abhängigkeit einbinden, etwa eine Chart-Bibliothek oder eine Kartenkomponente, deren Rendering in einer Testumgebung ohnehin wenig Aussagekraft hat und nur Zeit kostet. Statt die gesamte Teststrategie an dieser einen Komponente auszurichten, lässt sich gezielt nur sie stubben, während der Rest des Baums so realistisch wie nötig bleibt.
8. Performance bei tiefen Komponentenbäumen
Bei Design-System-Komponenten, die selbst wieder aus mehreren Ebenen von Unterkomponenten bestehen, etwa ein Datepicker, der intern Kalendergitter, Monatsnavigation und Eingabefeld als eigene Komponenten zusammensetzt, macht sich der Unterschied zwischen shallowMount und mount besonders deutlich bemerkbar. Ein mount-Test auf oberster Ebene rendert in einem solchen Fall unter Umständen dutzende Kindkomponenten mit, obwohl der Test eigentlich nur eine einzelne Prop-Weitergabe prüfen wollte.
In CI-Umgebungen mit mehreren tausend Tests summiert sich diese unnötige Renderarbeit zu einer messbaren Verzögerung der gesamten Pipeline. Ein pragmatischer Ansatz ist, komplexe, tief verschachtelte Komponenten grundsätzlich mit shallowMount als Standard zu testen und mount nur dort gezielt einzusetzen, wo tatsächlich eine mehrstufige Interaktion geprüft werden muss. Dieser Standard lässt sich in vielen Projekten sogar als Team-Konvention festhalten, um Diskussionen bei jedem einzelnen Test zu vermeiden.
9. Entscheidungshilfe für die Praxis
Als grobe Orientierung hilft die Frage, was genau ein Test eigentlich beweisen soll. Geht es um die interne Logik einer einzelnen Komponente, um bedingtes Rendering, Props-Verarbeitung oder lokalen State, ist shallowMount fast immer die richtige, schnellere Wahl, weil alles außerhalb dieser Grenze bewusst ausgeblendet wird. Geht es dagegen um das Zusammenspiel mehrerer Komponenten, insbesondere über Events, Slots oder Provide/Inject hinweg, ist mount notwendig, weil genau dieses Zusammenspiel sonst gar nicht stattfindet.
Ein guter Testcode-Review achtet deshalb nicht nur darauf, ob ein Test grün ist, sondern auch darauf, ob die gewählte Mount-Strategie zur eigentlichen Testabsicht passt. Ein mount-Test, der eigentlich nur eine isolierte Eigenschaft prüft, ist unnötig langsam und fragil gegenüber Änderungen in Kindkomponenten. Ein shallowMount-Test, der eigentlich Interaktion zwischen Komponenten prüfen will, liefert dagegen ein falsches Sicherheitsgefühl, weil die eigentlich kritische Verbindung nie wirklich ausgeführt wird.
| Kriterium | shallowMount | mount |
|---|---|---|
| Kindkomponenten | als Stub gerendert | vollständig ausgeführt |
| Testgeschwindigkeit | schneller, konstant | abhängig von Baumtiefe |
| Slot-Inhalte | nicht automatisch sichtbar | vollständig sichtbar |
| Provide/Inject | wird nicht real geprüft | wird real geprüft |
| Typischer Einsatz | isolierte Unit-Tests | Integrationstests |
Mironsoft
Vue-Architektur, Composition API und Nuxt-Performance
Vue-Anwendungen, die mit jedem Feature nicht komplizierter werden?
Wir prüfen bestehende Vue- und Nuxt-Projekte auf unstrukturierte Composables, ungenutzte Reaktivität und aufgeblähte Bundles und bauen daraus eine Architektur, die neue Features aufnimmt, ohne die Codebasis unübersichtlicher zu machen.
Architektur-Review
Composables, State-Management und Komponentenstruktur auf Wartbarkeit prüfen.
Performance-Audit
Reaktivitäts-Overhead, Bundle-Größe und Nuxt-Rendering-Strategie systematisch optimieren.
Nuxt-Integration
SSR/SSG-Setup und API-Anbindung robust und typsicher aufbauen.
10. Zusammenfassung
shallowMount vs. mount: Das Wichtigste auf einen Blick
Isolation
shallowMount stubbt alle Kindkomponenten automatisch
Vollständigkeit
mount rendert den kompletten Komponentenbaum
Slots
Slot-Inhalt von gestubbten Kindern bleibt bei shallowMount unsichtbar
Feinsteuerung
global.stubs erlaubt gezieltes Ausnahmen einzelner Komponenten