# Vue 3 testen in 2026: Vitest, Vue Test Utils en interviewvragen > Een praktische gids voor het testen van Vue in 2026: Vitest configureren, componenten mounten met Vue Test Utils, composables en Pinia-stores testen, API's mocken, coverage meten en de interviewvragen die teams stellen. - Published: 2026-06-21 - Updated: 2026-07-06 - Author: SharpSkill - Tags: vue, testing, vitest, vue-test-utils, interview - Reading time: 10 min --- Testen scheidt zelfverzekerde refactors van broze giswerk, en in 2026 heeft de Vue-toolchain zich rond twee bibliotheken gevormd: [Vitest](https://vitest.dev/guide/) als testrunner en [Vue Test Utils](https://test-utils.vuejs.org/) om componenten te mounten. Deze gids behandelt het opzetten van de stack, het testen van componenten en composables, het mocken van afhankelijkheden en het beantwoorden van de Vue-interviewvragen die teams echt stellen. > **De moderne Vue-teststack in 2026** > > De standaardaanbeveling uit de [Vue-documentatie](https://vuejs.org/guide/scaling-up/testing.html) is Vitest voor unit- en componenttests, Vue Test Utils als low-level mounting-API en Playwright of Cypress voor end-to-end-dekking. Vitest hergebruikt dezelfde Vite-configuratie als de applicatie, waardoor tests door exact dezelfde transformpijplijn lopen met vrijwel directe hot reload. ## Vitest configureren voor een Vue 3-project Vitest deelt de Vite-pijplijn, wat betekent dat één configuratiebestand zowel de dev-server als de testrunner aanstuurt. De optie `environment` moet op `jsdom` of `happy-dom` staan, zodat componenttests een DOM hebben om in te renderen. De vlag `globals: true` stelt `describe`, `it` en `expect` beschikbaar zonder ze in elk bestand te importeren. ```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)), }, }, }) ``` Met Vitest 3 werkt bovenstaande configuratie meteen na het installeren van `vitest`, `@vue/test-utils`, `@vitejs/plugin-vue` en `jsdom`. Doordat de alias de applicatieconfiguratie spiegelt, worden imports als `@/composables/useCart` in tests en productie op identieke wijze opgelost. ## Componenten mounten met Vue Test Utils Vue Test Utils biedt twee ingangen: `mount`, dat de volledige componentboom rendert, en `shallowMount`, dat kindcomponenten stubt. Voor de meeste unittests verdient `mount` de voorkeur, omdat het echte renderlogica uitvoert. De teruggegeven `wrapper` levert query-helpers als `find`, `get` en `text` om assertions op de gerenderde output te doen. ```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') }) }) ``` Elementen aanspreken via `data-test`-attributen in plaats van CSS-klassen houdt tests robuust: een stijlwijziging aan `.price--sale` breekt de selector niet, alleen een echte gedragsverandering doet dat. Deze ontkoppeling is een terugkerend thema in onderhoudbare Vue-tests. ## Gebruikersinteracties en emitted events testen Interactieve componenten vereisen assertions op wat er na een klik of invoer gebeurt. Vue Test Utils geeft een promise terug van `trigger`, en door daarop te awaiten wordt de reactiviteitswachtrij van Vue geleegd, zodat de DOM de update weerspiegelt. De helper `emitted` registreert elke custom event die een component uitstuurt, en zo worden de contracten tussen ouder en kind geverifieerd. ```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') }) }) ``` > **Await altijd de DOM-updates** > > Vue verwerkt reactieve updates asynchroon in batches. Vergeten te `await`-en op een `trigger`- of `setValue`-aanroep is de meest voorkomende oorzaak van flaky Vue-tests: de assertions draaien voordat de DOM opnieuw rendert, waardoor ze verouderde output lezen. Wanneer een statuswijziging buiten een event-helper plaatsvindt, importeer dan `nextTick` uit `vue` en gebruik `await nextTick()` vóór de assertie. ## Vue-composables geïsoleerd testen Composables die enkel reactiviteits-API's gebruiken (`ref`, `computed`, `watch`) hebben helemaal geen component nodig: het zijn gewone functies die reactieve status teruggeven, dus ze rechtstreeks aanroepen en op `.value` asserten is voldoende. Dit is de snelste en meest gerichte manier om bedrijfslogica te dekken, en het sluit goed aan bij de patronen uit [geavanceerde 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 afhangen van lifecycle-hooks zoals `onMounted` vormen de uitzondering: die moeten binnen een component-instantie draaien. De gebruikelijke oplossing is een minuscuul hulpcomponent dat de composable in `setup` aanroept en het resultaat blootstelt, waarna dat hulpcomponent met Vue Test Utils gemount wordt. ## API-aanroepen mocken met vi.mock Echte netwerkverzoeken maken tests traag en niet-deterministisch. Vitest vervangt modules met `vi.mock` en maakt spy-functies aan met `vi.fn`. Het onderstaande patroon mockt de module die `fetch` omhult, zodat de composable die getest wordt gecontroleerde data ontvangt zonder ooit een server te raken. ```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') }) }) ``` Mocken op de modulegrens laat de interne logica van de composable ongemoeid, terwijl elke test volledige controle krijgt over het API-antwoord, inclusief foutpaden. Door in plaats daarvan `mockRejectedValue` aan te roepen kan één test verifiëren dat fouten een errorstatus zetten en de spinner stoppen. ## Pinia-stores testen in 2026 Stores bevatten status die over componenten heen gedeeld wordt, en het officiële pakket `@pinia/testing` maakt ze eenvoudig te testen. `createTestingPinia` stubt standaard elke action, zodat een componenttest kan asserten dat een action werd gedispatcht zonder de bijwerkingen ervan uit te voeren. De [Pinia-testgids](https://pinia.vuejs.org/cookbook/testing.html) beveelt deze aanpak aan voor tests op componentniveau. ```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() }) }) ``` Om de storelogica zelf te testen in plaats van de integratie met een component, geef je `stubActions: false` mee, zodat de actions normaal draaien, en assert je vervolgens op de resulterende status. Deze scheiding tussen componenttests met gestubte actions en storetests met echte actions houdt elke suite gericht op één verantwoordelijkheid. ## Tests draaien in watch-modus en coverage meten Vitest levert een watch-modus die alleen de tests herdraait die door een opgeslagen bestand geraakt worden, wat de feedbackloop tijdens de ontwikkeling terugbrengt tot een fractie van een seconde. Coveragerapportage zit ingebouwd via de provider `@vitest/coverage-v8` en integreert soepel in een continuous-integration-pijplijn, waar een falende test of een coverageregressie een merge kan blokkeren. ```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/**'], }, }, }) ``` `vitest` op zichzelf starten activeert lokaal de watch-modus, terwijl `vitest run --coverage` een enkele doorloop uitvoert die geschikt is voor CI. Expliciete thresholds instellen maakt van coverage geen ijdele metriek maar een afgedwongen kwaliteitspoort: een pull request die de branch-coverage onder 75 procent laat zakken, faalt automatisch, wat bijdragers ertoe aanzet ook de paden te testen die ze toevoegen in plaats van alleen het happy path. ## Veelgestelde Vue-interviewvragen over testen, met antwoorden Vragen over testen duiken op in de meeste senior Vue-interviews, omdat ze onthullen hoe een kandidaat over onderhoudbaarheid nadenkt. De onderstaande antwoorden behandelen de concepten die het vaakst voorbijkomen, en de volledige set wordt geoefend in de [Vue-testing-interviewmodule](/technologies/vue-nuxt/interview-questions/vue-testing). **Wanneer gebruik je `shallowMount` in plaats van `mount`?** Grijp naar `shallowMount` wanneer een component zware of al geteste kinderen rendert en de test alleen om de eigen logica van de ouder geeft. Kinderen stubben isoleert de unit en versnelt het renderen, ten koste van het niet verifiëren van echte integratie tussen componenten. **Hoe worden asynchrone updates afgehandeld in Vue Test Utils?** Vue past reactieve wijzigingen toe op de volgende tick. Event-helpers als `trigger` en `setValue` geven promises terug die pas resolven nadat de DOM is bijgewerkt, dus daarop awaiten volstaat. Voor status die buiten die helpers wijzigt, forceert `await nextTick()` het legen van de wachtrij voordat de assertions draaien. **Wat is het verschil tussen `find` en `get`?** `find` geeft een lege wrapper terug wanneer geen enkel element matcht en werpt nooit een fout, wat past bij assertions als `expect(wrapper.find('.error').exists()).toBe(false)`. `get` werpt onmiddellijk een fout bij een ontbrekend element, wat het de betere keuze maakt wanneer het element verwacht wordt en de afwezigheid ervan een echte fout is. **Waarom `data-test`-attributen verkiezen boven klasse-selectors?** Klassenamen veranderen met styling en structuur, dus tests die ze bevragen breken bij cosmetische aanpassingen. Een aparte `data-test`-hook drukt de testintentie expliciet uit en overleeft refactors, wat valse negatieven vermindert. **Hoe test je een composable die op `onMounted` steunt?** Lifecycle-hooks vuren alleen binnen een component-instantie, dus de composable kan niet rechtstreeks worden aangeroepen. De standaardtechniek is een wegwerp-harnescomponent dat de composable in `setup` aanroept en de returnwaarde blootstelt, waarna je dat harnas met Vue Test Utils mount en via de wrapper assert. Zo blijft de reactiviteits- en lifecycle-bedrading intact terwijl de te testen logica toch geïsoleerd blijft. Meer conceptuele en coding-vragen zijn verzameld in de gids [essentiële Vue.js-interviewvragen](/blog/vue-nuxt/essential-vuejs-interview-questions). ## Conclusie Een betrouwbare Vue-testopzet in 2026 komt neer op een handvol bewuste keuzes: - Draai Vitest met `environment: 'jsdom'` en de Vue-plugin, zodat tests exact dezelfde Vite-transformpijplijn delen als de applicatie. - Verkies `mount` boven `shallowMount`, tenzij een kindcomponent zwaar is of elders al gedekt wordt. - Await altijd `trigger`, `setValue` en `nextTick` voor de assertions om flaky, timingafhankelijke tests uit te sluiten. - Test composables die enkel reactiviteits-API's gebruiken door ze rechtstreeks aan te roepen en op `.value` te asserten. - Mock op de modulegrens met `vi.mock`, zodat netwerkgedrag deterministisch is en foutpaden testbaar zijn. - Gebruik `createTestingPinia({ createSpy: vi.fn })` voor componenttests en `stubActions: false` bij het testen van storelogica. - Bevraag elementen via `data-test`-attributen, zodat tests alleen breken op echte gedragsveranderingen en nooit op styling. Beheers deze patronen en testen wordt een ontwerpinstrument in plaats van een karwei, dat regressies opvangt voordat ze productie bereiken. Verken het volledige [Vue- en Nuxt-leertraject](/technologies/vue-nuxt) om je interviewklare vaardigheden verder uit te bouwen. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/nl/blog/vue-nuxt/vue-3-testing-vitest-vue-test-utils