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.

Flusso di lavoro per il testing Vue con Vitest e Vue Test Utils nel 2026

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.

Lo stack moderno per il testing in Vue nel 2026

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.

vitest.config.tstypescript
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.

PriceTag.spec.tstypescript
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.

SearchBar.spec.tstypescript
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')
  })
})
Attendere sempre gli aggiornamenti del DOM

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.

useCart.spec.tstypescript
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.

useProducts.spec.tstypescript
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.

CheckoutButton.spec.tstypescript
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.

vitest.config.ts (coverage section)typescript
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 mount a shallowMount, a meno che un componente figlio sia pesante o già coperto altrove.
  • Usare sempre await su trigger, setValue e nextTick prima 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 e stubActions: false quando 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

#vue
#testing
#vitest
#vue-test-utils
#interview

Condividi

Articoli correlati