Testing in Vue 3 nel 2026: Vitest, Vue Test Utils e domande da colloquio
Guida pratica al testing in Vue nel 2026: configurazione di Vitest, montaggio dei componenti con Vue Test Utils, test di composable e store Pinia, mock delle API, misurazione della copertura e le domande da colloquio poste dai team di selezione.

Il testing in Vue distingue i refactoring sicuri dalle scommesse fragili, e nel 2026 la toolchain si è consolidata attorno a due librerie: Vitest come test runner e Vue Test Utils per il montaggio dei componenti. Questa guida illustra la configurazione dello stack, il test di componenti e composable, il mock delle dipendenze e le risposte alle domande da colloquio sul testing in Vue che i team di selezione pongono davvero.
La raccomandazione predefinita della documentazione di Vue prevede Vitest per i test unitari e dei componenti, Vue Test Utils come API di montaggio a basso livello e Playwright o Cypress per la copertura end-to-end. Vitest riutilizza la stessa configurazione Vite dell'applicazione, quindi i test passano per la pipeline di trasformazione identica, con un hot reload quasi istantaneo.
Configurare Vitest per un progetto Vue 3
Vitest condivide la pipeline di Vite, il che significa che un unico file di configurazione governa sia il dev server sia il test runner. L'opzione environment deve essere impostata su jsdom o happy-dom, così i test dei componenti dispongono di un DOM su cui renderizzare. Il flag globals: true espone describe, it ed expect senza doverli importare in ogni file.
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)),
},
},
})Con Vitest 3 la configurazione qui sopra funziona da subito dopo l'installazione di vitest, @vue/test-utils, @vitejs/plugin-vue e jsdom. Poiché l'alias rispecchia quello dell'applicazione, import come @/composables/useCart si risolvono in modo identico nei test e in produzione.
Montare i componenti con Vue Test Utils
Vue Test Utils espone due punti di ingresso: mount, che renderizza l'intero albero del componente, e shallowMount, che sostituisce i componenti figli con degli stub. Per la maggior parte dei test unitari mount è la scelta preferibile, perché mette alla prova il comportamento di rendering reale. Il wrapper restituito offre helper di interrogazione come find, get e text per verificare l'output renderizzato.
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')
})
})Raggiungere gli elementi tramite attributi data-test anziché tramite classi CSS mantiene i test resilienti: una modifica di stile a .price--sale non romperà il selettore, solo un cambiamento reale di comportamento lo farà. Questo disaccoppiamento è un tema ricorrente in un testing Vue manutenibile.
Testare eventi utente ed eventi emessi
I componenti interattivi richiedono asserzioni su ciò che accade dopo un clic o un input. Vue Test Utils restituisce una promise da trigger, e attenderla svuota la coda di reattività di Vue affinché il DOM rifletta l'aggiornamento. L'helper emitted registra ogni evento personalizzato emesso da un componente, ed è così che si verificano i contratti tra genitore e figlio.
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 applica gli aggiornamenti reattivi in modo asincrono. Dimenticare di usare await su una chiamata a trigger o setValue è la causa più comune di test Vue instabili: le asserzioni vengono eseguite prima che il DOM venga renderizzato di nuovo, quindi leggono un output obsoleto. Quando un cambiamento di stato avviene al di fuori di un helper di evento, occorre importare nextTick da vue e usare await nextTick() prima di verificare.
Testare i composable Vue in isolamento
I composable che usano solo le API di reattività (ref, computed, watch) non hanno bisogno di alcun componente: sono semplici funzioni che restituiscono uno stato reattivo, quindi chiamarle direttamente e verificare .value è sufficiente. È il modo più rapido e mirato per coprire la logica di business, e si sposa bene con i pattern descritti nei composable avanzati in Vue 3.
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)
})
})I composable che dipendono da hook del ciclo di vita come onMounted sono l'eccezione: devono essere eseguiti all'interno di un'istanza di componente. La soluzione comune è un minuscolo componente di supporto che chiama il composable in setup ed espone il risultato, per poi montare quel componente con Vue Test Utils.
Pronto a superare i tuoi colloqui su Vue.js / Nuxt.js?
Pratica con i nostri simulatori interattivi, flashcards e test tecnici.
Mock delle chiamate API con vi.mock
Le richieste di rete reali rendono i test lenti e non deterministici. Vitest sostituisce i moduli con vi.mock e crea funzioni spia con vi.fn. Il pattern seguente esegue il mock del modulo che avvolge fetch, così il composable sotto test riceve dati controllati senza mai raggiungere un server.
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')
})
})Eseguire il mock al confine del modulo lascia intatta la logica interna del composable, dando al contempo a ogni test il pieno controllo sulla risposta dell'API, inclusi i percorsi di errore. Chiamare invece mockRejectedValue permette a un singolo test di verificare che i fallimenti impostino uno stato di errore e fermino lo spinner.
Testare gli store Pinia nel 2026
Gli store contengono stato condiviso tra più componenti, e il pacchetto ufficiale @pinia/testing ne semplifica il test. createTestingPinia sostituisce per impostazione predefinita ogni action con uno stub, così un test di componente può verificare che un'action sia stata dispatchata senza eseguirne gli effetti collaterali. La guida al testing di Pinia raccomanda questo approccio per i test a livello di componente.
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()
})
})Per testare la logica dello store in sé, anziché l'integrazione con il componente, si passa stubActions: false così le action vengono eseguite normalmente, per poi verificare lo stato risultante. Questa separazione tra test di componente con action sostituite e test di store con action reali mantiene ogni suite concentrata su una singola responsabilità.
Eseguire i test in watch mode e misurare la copertura
Vitest include una watch mode che riesegue solo i test interessati da un file salvato, riducendo il ciclo di feedback durante lo sviluppo a una frazione di secondo. Il report di copertura è integrato tramite il provider @vitest/coverage-v8 e si inserisce senza attriti in una pipeline di integrazione continua, dove un test fallito o una regressione della copertura può bloccare un merge.
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/**'],
},
},
})Eseguire vitest da solo avvia la watch mode in locale, mentre vitest run --coverage esegue un unico passaggio adatto alla CI. Impostare soglie esplicite trasforma la copertura da metrica di facciata in un cancello di qualità imposto: una pull request che porta la copertura dei rami sotto il 75 percento fallisce automaticamente, spingendo chi contribuisce a testare i percorsi che aggiunge anziché solo il caso ideale.
Domande e risposte frequenti da colloquio sul testing in Vue
Le domande sul testing emergono nella maggior parte dei colloqui Vue senior perché rivelano come un candidato ragiona sulla manutenibilità. Le risposte che seguono coprono i concetti più ricorrenti, mentre l'insieme completo viene approfondito nel modulo di domande da colloquio sul testing in Vue.
Quando conviene usare shallowMount invece di mount? Si ricorre a shallowMount quando un componente renderizza figli pesanti o già testati e il test riguarda solo la logica del genitore. Sostituire i figli con stub isola l'unità e velocizza il rendering, al prezzo di non verificare l'integrazione reale tra i componenti.
Come si gestiscono gli aggiornamenti asincroni in Vue Test Utils? Vue applica i cambiamenti reattivi al tick successivo. Gli helper di evento come trigger e setValue restituiscono promise che si risolvono dopo l'aggiornamento del DOM, quindi attenderle è sufficiente. Per lo stato modificato al di fuori di questi helper, await nextTick() forza lo svuotamento della coda prima che vengano eseguite le asserzioni.
Qual è la differenza tra find e get? find restituisce un wrapper vuoto quando nessun elemento corrisponde e non solleva mai un'eccezione, il che si adatta ad asserzioni come expect(wrapper.find('.error').exists()).toBe(false). get solleva immediatamente un'eccezione su un elemento mancante, rendendolo la scelta migliore quando ci si aspetta che l'elemento esista e la sua assenza è un fallimento autentico.
Perché preferire gli attributi data-test ai selettori di classe? I nomi delle classi cambiano con lo stile e la struttura, quindi i test che li interrogano si rompono con modifiche cosmetiche. Un hook data-test dedicato esprime esplicitamente l'intento del test e sopravvive ai refactoring, riducendo i falsi negativi.
Come si testa un composable che dipende da onMounted? Gli hook del ciclo di vita scattano solo all'interno di un'istanza di componente, quindi il composable non può essere chiamato direttamente. La tecnica standard prevede un componente usa e getta che invoca il composable in setup ed espone il suo valore di ritorno, per poi montare quel componente con Vue Test Utils e verificare tramite il wrapper. In questo modo la reattività e il cablaggio del ciclo di vita restano intatti, isolando comunque la logica sotto test. Altre domande concettuali e di programmazione sono raccolte nella guida alle domande essenziali da colloquio su Vue.js.
Conclusione
Una configurazione di testing Vue affidabile nel 2026 si riduce a poche scelte deliberate:
- Eseguire Vitest con
environment: 'jsdom'e il plugin Vue, così i test condividono la pipeline di trasformazione Vite esatta dell'applicazione. - Preferire
mountashallowMount, a meno che un componente figlio sia pesante o già coperto altrove. - Usare sempre
awaitsutrigger,setValueenextTickprima di verificare, per eliminare i test instabili e dipendenti dal timing. - Testare i composable che usano solo le API di reattività chiamandoli direttamente e verificando
.value. - Eseguire il mock al confine del modulo con
vi.mock, così il comportamento di rete è deterministico e i percorsi di errore sono testabili. - Usare
createTestingPinia({ createSpy: vi.fn })per i test di componente estubActions: falsequando si mette alla prova la logica dello store. - Interrogare gli elementi tramite attributi
data-test, così i test si rompono solo su cambiamenti reali di comportamento, mai sullo stile.
Padroneggiare questi pattern trasforma il testing in uno strumento di progettazione anziché in una scocciatura, intercettando le regressioni prima che raggiungano la produzione. Si esplori il percorso di apprendimento completo su Vue e Nuxt per continuare a costruire competenze pronte per il colloquio.
Inizia a praticare!
Metti alla prova le tue conoscenze con i nostri simulatori di colloquio e test tecnici.
Tag
Condividi
Articoli correlati

Composable Avanzati in Vue 3: Pattern Riutilizzabili e Domande da Colloquio 2026
Guida completa ai composable avanzati di Vue 3 con pattern riutilizzabili, gestione asincrona degli errori, dependency injection con provide/inject e validazione dei form. Include domande da colloquio tecnico aggiornate al 2026.

Vue 3 Pinia vs Vuex: Gestione dello Stato Moderna e Domande da Colloquio 2026
Pinia vs Vuex a confronto: design delle API, supporto TypeScript, performance, strategie di migrazione e domande frequenti sulla gestione dello stato Vue nei colloqui 2026.

Nuxt 4 nel 2026: nuova struttura di directory e migrazione da Nuxt 3
Analisi approfondita delle novità di Nuxt 4 nel 2026: struttura app/, data fetching con cache evoluto, reattività shallow, TypeScript rigoroso e guida alla migrazione graduale da Nuxt 3.