# Vue-3-Testing 2026: Vitest, Vue Test Utils und Interviewfragen > Ein praxisnaher Leitfaden zum Vue-Testing 2026: Vitest konfigurieren, Komponenten mit Vue Test Utils mounten, Composables und Pinia-Stores testen, APIs mocken, Coverage messen und die Interviewfragen, die Recruiting-Teams stellen. - Published: 2026-06-21 - Updated: 2026-07-06 - Author: SharpSkill - Tags: vue, testing, vitest, vue-test-utils, interview - Reading time: 10 min --- Vue-Testing trennt selbstbewusste Refactorings von fragilem Rätselraten, und 2026 hat sich die Toolchain auf zwei Bibliotheken verdichtet: [Vitest](https://vitest.dev/guide/) als Testrunner und [Vue Test Utils](https://test-utils.vuejs.org/) zum Mounten von Komponenten. Dieser Leitfaden zeigt, wie sich der Stack konfigurieren lässt, wie Komponenten und Composables getestet werden, wie Abhängigkeiten gemockt werden und wie sich die Vue-Testing-Interviewfragen beantworten lassen, die Recruiting-Teams tatsächlich stellen. > **Der moderne Vue-Testing-Stack 2026** > > Die Standardempfehlung aus der [Vue-Dokumentation](https://vuejs.org/guide/scaling-up/testing.html) lautet: Vitest für Unit- und Komponententests, Vue Test Utils als Low-Level-Mounting-API und Playwright oder Cypress für End-to-End-Abdeckung. Vitest verwendet dieselbe Vite-Konfiguration wie die Anwendung, sodass Tests durch dieselbe Transform-Pipeline laufen und Änderungen nahezu ohne Verzögerung neu geladen werden. ## Vitest für ein Vue-3-Projekt konfigurieren Vitest teilt sich die Vite-Pipeline, was bedeutet, dass eine einzige Konfigurationsdatei sowohl den Dev-Server als auch den Testrunner steuert. Die Option `environment` muss auf `jsdom` oder `happy-dom` gesetzt werden, damit Komponententests ein DOM zum Rendern haben. Das Flag `globals: true` stellt `describe`, `it` und `expect` bereit, ohne dass sie in jeder Datei importiert werden müssen. ```typescript // vitest.config.ts import { defineConfig } from 'vitest/config' import vue from '@vitejs/plugin-vue' import { fileURLToPath } from 'node:url' export default defineConfig({ plugins: [vue()], test: { // jsdom provides document/window so components can mount environment: 'jsdom', // expose describe/it/expect globally globals: true, // run this before each test file (matchers, cleanup) setupFiles: ['./tests/setup.ts'], // only pick up *.spec.ts and *.test.ts files include: ['**/*.{test,spec}.ts'], }, resolve: { alias: { // mirror the @ alias used across the app '@': fileURLToPath(new URL('./src', import.meta.url)), }, }, }) ``` Mit Vitest 3 funktioniert die obige Konfiguration sofort, nachdem `vitest`, `@vue/test-utils`, `@vitejs/plugin-vue` und `jsdom` installiert wurden. Da der Alias die Anwendungskonfiguration spiegelt, werden Importe wie `@/composables/useCart` in Tests und Produktion identisch aufgelöst. ## Komponenten mit Vue Test Utils mounten Vue Test Utils bietet zwei Einstiegspunkte: `mount`, das den vollständigen Komponentenbaum rendert, und `shallowMount`, das Kindkomponenten stubbt. Für die meisten Unit-Tests ist `mount` vorzuziehen, weil es das echte Renderverhalten prüft. Der zurückgegebene `wrapper` stellt Abfrage-Helfer wie `find`, `get` und `text` bereit, um gegen die gerenderte Ausgabe zu prüfen. ```typescript // PriceTag.spec.ts import { describe, it, expect } from 'vitest' import { mount } from '@vue/test-utils' import PriceTag from '@/components/PriceTag.vue' describe('PriceTag', () => { it('formats the amount as currency', () => { const wrapper = mount(PriceTag, { // props are passed through the mounting options props: { amount: 4200, currency: 'EUR' }, }) // get() throws if the selector is missing, unlike find() expect(wrapper.get('[data-test="price"]').text()).toBe('€42.00') }) it('applies a discount class when on sale', () => { const wrapper = mount(PriceTag, { props: { amount: 4200, currency: 'EUR', onSale: true }, }) // classes() returns the array of applied CSS classes expect(wrapper.get('[data-test="price"]').classes()).toContain('price--sale') }) }) ``` Elemente über `data-test`-Attribute statt über CSS-Klassen anzusteuern hält Tests widerstandsfähig: Eine Styling-Änderung an `.price--sale` bricht den Selektor nicht, nur eine echte Verhaltensänderung tut das. Diese Entkopplung ist ein wiederkehrendes Motiv in wartbarem Vue-Testing. ## Benutzeraktionen und ausgelöste Events testen Interaktive Komponenten brauchen Assertions darüber, was nach einem Klick oder einer Eingabe passiert. Vue Test Utils gibt aus `trigger` ein Promise zurück, und das Abwarten davon leert die Reaktivitäts-Queue von Vue, sodass das DOM die Aktualisierung widerspiegelt. Der Helfer `emitted` protokolliert jedes benutzerdefinierte Event, das eine Komponente ausgelöst hat, und genau so werden die Verträge zwischen Eltern- und Kindkomponente überprüft. ```typescript // SearchBar.spec.ts import { describe, it, expect } from 'vitest' import { mount } from '@vue/test-utils' import SearchBar from '@/components/SearchBar.vue' describe('SearchBar', () => { it('emits search with the typed query', async () => { const wrapper = mount(SearchBar) // setValue writes to the input and fires an input event await wrapper.get('input').setValue('vitest') // awaiting trigger flushes the reactivity queue before assertions await wrapper.get('[data-test="submit"]').trigger('click') // emitted() returns a record of every event and its payloads const events = wrapper.emitted('search') expect(events).toHaveLength(1) // events[0] is the first emission, [0] its first argument expect(events![0][0]).toBe('vitest') }) }) ``` > **DOM-Aktualisierungen immer abwarten** > > Vue verarbeitet reaktive Aktualisierungen asynchron in Batches. Ein vergessenes `await` bei einem `trigger`- oder `setValue`-Aufruf ist die häufigste Ursache für flaky Vue-Tests: Die Assertions laufen, bevor das DOM neu gerendert wurde, und lesen deshalb veraltete Ausgabe. Wenn eine Zustandsänderung außerhalb eines Event-Helfers geschieht, sollte `nextTick` aus `vue` importiert und vor der Assertion `await nextTick()` aufgerufen werden. ## Vue-Composables isoliert testen Composables, die nur Reaktivitäts-APIs verwenden (`ref`, `computed`, `watch`), brauchen überhaupt keine Komponente: Es sind schlichte Funktionen, die reaktiven Zustand zurückgeben, sodass ein direkter Aufruf und eine Assertion auf `.value` ausreichen. Das ist der schnellste und fokussierteste Weg, Geschäftslogik abzudecken, und passt gut zu den Mustern in [fortgeschrittenen Vue-3-Composables](/blog/vue-nuxt/advanced-vue-3-composables-reusable-patterns). ```typescript // useCart.spec.ts import { describe, it, expect } from 'vitest' import { useCart } from '@/composables/useCart' describe('useCart', () => { it('tracks total price as items change', () => { // composables using only ref/computed run without mounting const { items, total, addItem } = useCart() expect(total.value).toBe(0) addItem({ id: 1, price: 1500, qty: 2 }) // computed total recalculates synchronously on state change expect(total.value).toBe(3000) addItem({ id: 2, price: 500, qty: 1 }) expect(items.value).toHaveLength(2) expect(total.value).toBe(3500) }) }) ``` Composables, die von Lifecycle-Hooks wie `onMounted` abhängen, sind die Ausnahme: Sie müssen innerhalb einer Komponenteninstanz laufen. Der übliche Ausweg ist eine winzige Helfer-Komponente, die das Composable in `setup` aufruft und das Ergebnis bereitstellt, und die dann mit Vue Test Utils gemountet wird. ## API-Aufrufe mit vi.mock mocken Echte Netzwerkanfragen machen Tests langsam und nicht-deterministisch. Vitest ersetzt Module mit `vi.mock` und erzeugt Spy-Funktionen mit `vi.fn`. Das folgende Muster mockt das Modul, das `fetch` kapselt, sodass das getestete Composable kontrollierte Daten erhält, ohne jemals einen Server zu treffen. ```typescript // useProducts.spec.ts import { describe, it, expect, vi, beforeEach } from 'vitest' import { useProducts } from '@/composables/useProducts' import { getProducts } from '@/api/products' // replace the whole module with mocked exports vi.mock('@/api/products', () => ({ getProducts: vi.fn(), })) describe('useProducts', () => { beforeEach(() => { // reset call history between tests to avoid leakage vi.mocked(getProducts).mockReset() }) it('exposes fetched products and clears loading', async () => { // define what the mocked API returns for this test vi.mocked(getProducts).mockResolvedValue([ { id: 1, name: 'Keyboard' }, ]) const { products, loading, load } = useProducts() await load() expect(getProducts).toHaveBeenCalledOnce() expect(loading.value).toBe(false) expect(products.value[0].name).toBe('Keyboard') }) }) ``` Das Mocken an der Modulgrenze lässt die interne Logik des Composables unangetastet und gibt jedem Test die volle Kontrolle über die API-Antwort, einschließlich der Fehlerpfade. Ein `mockRejectedValue` an dieser Stelle ermöglicht es einem einzelnen Test, zu überprüfen, dass Fehler einen Fehlerzustand setzen und den Spinner stoppen. ## Pinia-Stores 2026 testen Stores halten komponentenübergreifenden Zustand, und das offizielle Paket `@pinia/testing` macht sie unkompliziert testbar. `createTestingPinia` stubbt standardmäßig jede Action, sodass ein Komponententest prüfen kann, dass eine Action ausgelöst wurde, ohne deren Seiteneffekte auszuführen. Der [Pinia-Testing-Leitfaden](https://pinia.vuejs.org/cookbook/testing.html) empfiehlt diesen Ansatz für Tests auf Komponentenebene. ```typescript // CheckoutButton.spec.ts import { describe, it, expect, vi } from 'vitest' import { mount } from '@vue/test-utils' import { createTestingPinia } from '@pinia/testing' import CheckoutButton from '@/components/CheckoutButton.vue' import { useCartStore } from '@/stores/cart' describe('CheckoutButton', () => { it('dispatches checkout on click', async () => { const wrapper = mount(CheckoutButton, { global: { plugins: [ // vi.fn spies on actions instead of executing them createTestingPinia({ createSpy: vi.fn }), ], }, }) const store = useCartStore() await wrapper.get('[data-test="checkout"]').trigger('click') // the action is stubbed, so only the dispatch is asserted expect(store.checkout).toHaveBeenCalledOnce() }) }) ``` Um die Store-Logik selbst statt der Komponentenintegration zu testen, sollte `stubActions: false` übergeben werden, damit Actions normal ausgeführt werden, um anschließend gegen den resultierenden Zustand zu prüfen. Diese Trennung zwischen Komponententests mit gestubbten Actions und Store-Tests mit echten Actions hält jede Testsuite auf eine einzige Verantwortung fokussiert. ## Tests im Watch-Modus ausführen und Coverage messen Vitest bringt einen Watch-Modus mit, der nur die Tests erneut ausführt, die von einer gespeicherten Datei betroffen sind, was die Feedback-Schleife während der Entwicklung auf einen Bruchteil einer Sekunde verkürzt. Das Coverage-Reporting ist über den Provider `@vitest/coverage-v8` eingebaut und integriert sich sauber in eine Continuous-Integration-Pipeline, in der ein fehlschlagender Test oder eine Coverage-Regression einen Merge blockieren kann. ```typescript // vitest.config.ts (coverage section) export default defineConfig({ test: { coverage: { // v8 is the fastest provider and needs no instrumentation step provider: 'v8', reporter: ['text', 'html', 'lcov'], // fail CI when a threshold drops below the target thresholds: { statements: 80, branches: 75, functions: 80, }, // exclude generated and config files from the report exclude: ['**/*.config.ts', '**/types/**'], }, }, }) ``` Ein blankes `vitest` startet lokal den Watch-Modus, während `vitest run --coverage` einen einzelnen Durchlauf ausführt, der für CI geeignet ist. Explizite Schwellenwerte machen aus Coverage statt einer Vanity-Metrik ein erzwungenes Qualitätsgate: Ein Pull Request, der die Branch-Coverage unter 75 Prozent drückt, schlägt automatisch fehl, was Beitragende dazu bewegt, die Pfade zu testen, die sie hinzufügen, statt nur den Happy Path. ## Häufige Vue-Testing-Interviewfragen und Antworten Testing-Fragen tauchen in den meisten Senior-Vue-Interviews auf, weil sie zeigen, wie ein Kandidat über Wartbarkeit denkt. Die folgenden Antworten decken die Konzepte ab, die am häufigsten vorkommen, und der vollständige Satz wird im [Vue-Testing-Interviewmodul](/technologies/vue-nuxt/interview-questions/vue-testing) geübt. **Wann sollte `shallowMount` statt `mount` verwendet werden?** Zu `shallowMount` sollte gegriffen werden, wenn eine Komponente schwere oder bereits getestete Kinder rendert und der Test sich nur für die eigene Logik der Elternkomponente interessiert. Das Stubben der Kinder isoliert die Unit und beschleunigt das Rendern, allerdings um den Preis, dass die echte Integration zwischen den Komponenten nicht überprüft wird. **Wie werden asynchrone Aktualisierungen in Vue Test Utils behandelt?** Vue wendet reaktive Änderungen im nächsten Tick an. Event-Helfer wie `trigger` und `setValue` geben Promises zurück, die nach der DOM-Aktualisierung auflösen, sodass ihr Abwarten genügt. Bei Zuständen, die außerhalb dieser Helfer geändert werden, erzwingt `await nextTick()` das Leeren der Queue, bevor die Assertions laufen. **Worin unterscheiden sich `find` und `get`?** `find` gibt einen leeren Wrapper zurück, wenn kein Element passt, und wirft nie einen Fehler, was zu Assertions wie `expect(wrapper.find('.error').exists()).toBe(false)` passt. `get` wirft sofort einen Fehler bei einem fehlenden Element und ist damit die bessere Wahl, wenn das Element erwartet wird und sein Fehlen ein echter Fehler ist. **Warum sind `data-test`-Attribute Klassenselektoren vorzuziehen?** Klassennamen ändern sich mit Styling und Struktur, sodass Tests, die sie abfragen, bei kosmetischen Änderungen brechen. Ein dedizierter `data-test`-Hook drückt die Test-Absicht explizit aus und übersteht Refactorings, was falsch negative Ergebnisse reduziert. **Wie wird ein Composable getestet, das sich auf `onMounted` stützt?** Lifecycle-Hooks feuern nur innerhalb einer Komponenteninstanz, sodass das Composable nicht direkt aufgerufen werden kann. Die Standardtechnik ist eine Wegwerf-Harness-Komponente, die das Composable in `setup` aufruft und dessen Rückgabewert bereitstellt, um diese Harness dann mit Vue Test Utils zu mounten und über den Wrapper zu prüfen. So bleibt die Verdrahtung von Reaktivität und Lifecycle intakt, während die getestete Logik trotzdem isoliert wird. Weitere konzeptionelle und Coding-Fragen sind im Leitfaden zu den [essenziellen Vue.js-Interviewfragen](/blog/vue-nuxt/essential-vuejs-interview-questions) gesammelt. ## Fazit Ein verlässliches Vue-Testing-Setup läuft 2026 auf wenige bewusste Entscheidungen hinaus: - Vitest mit `environment: 'jsdom'` und dem Vue-Plugin betreiben, damit Tests die exakte Vite-Transform-Pipeline der Anwendung teilen. - `mount` gegenüber `shallowMount` bevorzugen, außer eine Kindkomponente ist schwer oder anderswo bereits abgedeckt. - `trigger`, `setValue` und `nextTick` vor der Assertion immer `await`en, um flaky, zeitabhängige Tests zu vermeiden. - Composables, die nur Reaktivitäts-APIs verwenden, durch direkten Aufruf und eine Assertion auf `.value` testen. - An der Modulgrenze mit `vi.mock` mocken, damit das Netzwerkverhalten deterministisch ist und Fehlerpfade testbar sind. - `createTestingPinia({ createSpy: vi.fn })` für Komponententests nutzen und `stubActions: false`, wenn Store-Logik geprüft wird. - Elemente über `data-test`-Attribute abfragen, damit Tests nur bei echten Verhaltensänderungen brechen, nie beim Styling. Wer diese Muster beherrscht, macht aus Testing ein Design-Werkzeug statt einer lästigen Pflicht und fängt Regressionen ab, bevor sie in die Produktion gelangen. Der vollständige [Vue- und Nuxt-Lernpfad](/technologies/vue-nuxt) hilft dabei, weiter interviewreife Fähigkeiten aufzubauen. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/de/blog/vue-nuxt/vue-3-testing-vitest-vue-test-utils