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.

Testen scheidt zelfverzekerde refactors van broze giswerk, en in 2026 heeft de Vue-toolchain zich rond twee bibliotheken gevormd: Vitest als testrunner en Vue Test Utils 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 standaardaanbeveling uit de Vue-documentatie 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.
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.
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.
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')
})
})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.
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.
Klaar om je Vue.js / Nuxt.js gesprekken te halen?
Oefen met onze interactieve simulatoren, flashcards en technische tests.
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.
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 beveelt deze aanpak aan voor tests op componentniveau.
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.
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.
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.
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
mountbovenshallowMount, tenzij een kindcomponent zwaar is of elders al gedekt wordt. - Await altijd
trigger,setValueennextTickvoor 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
.valuete asserten. - Mock op de modulegrens met
vi.mock, zodat netwerkgedrag deterministisch is en foutpaden testbaar zijn. - Gebruik
createTestingPinia({ createSpy: vi.fn })voor componenttests enstubActions: falsebij 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 om je interviewklare vaardigheden verder uit te bouwen.
Begin met oefenen!
Test je kennis met onze gespreksimulatoren en technische tests.
Tags
Delen
Gerelateerde artikelen

Geavanceerde Vue 3 Composables: Herbruikbare Patronen en Interviewvragen 2026
Een diepgaande gids over geavanceerde Vue 3 composable-patronen, van async data-ophaling tot formuliervalidatie en dependency injection. Inclusief veelgestelde interviewvragen over de Composition API in 2026.

Vue 3 Pinia vs Vuex: Modern State Management en Sollicitatievragen 2026
Pinia vs Vuex vergeleken: API-ontwerp, TypeScript-ondersteuning, performance, migratiestrategieën en veelgestelde Vue state management sollicitatievragen voor 2026.

Nuxt 4 in 2026: Nieuwe Directorystructuur en Migratie vanuit Nuxt 3
Uitgebreide gids over Nuxt 4 in 2026: de nieuwe app/-directorystructuur, shallow reactivity, verbeterde TypeScript-integratie, Unhead v2 en een praktische migratiechecklist voor Nuxt 3-projecten.