# Come usare Vitest in un progetto React: comandi, mock e soglie

Vitest si governa con quattro cose: `vi.fn`, `vi.mock`, le soglie di copertura e `--changed`. Cosa controlla davvero e cosa no.

> Pagina: https://ui.eneawork.it/blog/come-usare-vitest
> Markdown: https://ui.eneawork.it/blog/come-usare-vitest.md

*2026-11-01*

Vitest si governa con tre comandi: vitest run per la CI, vitest in ascolto e vitest run --coverage per le soglie. La versione misurata è vitest 5.0.5.

Il pacchetto vitest 5.0.5 ha 169.698.842 download nella settimana chiusa il 1 novembre 2026, letti da api.npmjs.org/downloads/point/last-week/vitest; @vitest/coverage-v8 ne ha 59.085.379. Il catalogo EneUI conta 111.985 componenti da 1.472 librerie, misurati da /api/stats lo stesso giorno.

## Dove vive la configurazione

import { defineConfig } from "vitest/config"; import react from "@vitejs/plugin-react"; export default defineConfig({ plugins: [react()], test: { environment: "jsdom", setupFiles: ["./src/test/setup.ts"], coverage: { provider: "v8", reporter: ["text", "html"], thresholds: { lines: 80, functions: 80, branches: 70 }, }, }, });

Il blocco test lo legge solo Vitest: Vite lo ignora in build. environment: "jsdom" dà un DOM finto, setupFiles carica jest-dom e il cleanup una volta per run.

## Mock di una funzione

import { afterEach, expect, test, vi } from "vitest"; import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { LoginForm } from "./LoginForm"; afterEach(() => vi.restoreAllMocks()); test("invia le credenziali", async () => { const onSubmit = vi.fn(); render(); await userEvent.type(screen.getByLabelText("Email"), "a@b.it"); await userEvent.type(screen.getByLabelText("Password"), "segreta"); await userEvent.click(screen.getByRole("button", { name: "Accedi" })); expect(onSubmit).toHaveBeenCalledWith({ email: "a@b.it", password: "segreta" }); });

vi.fn registra ogni chiamata con i suoi argomenti. afterEach con restoreAllMocks riporta i mock allo stato originale fra un test e l'altro, senza contagio.

## Mock di un modulo

import { beforeEach, expect, test, vi } from "vitest"; import { fetchRates } from "./rates"; vi.mock("./rates", () => ({ fetchRates: vi.fn() })); beforeEach(() => { vi.mocked(fetchRates).mockResolvedValue({ eur: 1 }); }); test("mostra il cambio", async () => { const rate = await fetchRates("usd"); expect(rate.eur).toBe(1); });

vi.mock viene spostato in cima al file dal trasformatore, quindi funziona anche scritto dopo gli import. La factory deve restituire ogni export che il codice sotto test usa, altrimenti l'import arriva vuoto.

## Soglie di copertura in CI

npm i -D @vitest/coverage-v8 npx vitest run --coverage

Con thresholds configurate, il comando esce con codice uno quando la copertura scende sotto la soglia. Senza soglia il numero resta un report che nessuno guarda.

## Comandi utili

npx vitest run -t "invia le credenziali" npx vitest --changed HEAD~1 npx vitest run --bail=1 npx vitest run --project=unit

-t filtra per nome del test, --changed esegue solo i file toccati dal diff rispetto al riferimento, --bail=1 si ferma al primo rosso. --project sceglie un progetto quando la configurazione ne dichiara più di uno.

## Limite dichiarato

jsdom non applica il CSS e non calcola il layout: un test verde dice che la logica funziona, non che il componente si vede. La copertura, inoltre, conta le righe eseguite, non le asserzioni: un file importato senza un expect resta coperto.

## Dove continuare

Tre componenti su cui provare la suite: sign-in, toast e otp-input; il resto è in components/lib e nelle 1.556 anteprime. La configurazione iniziale è in Vitest: setup in dieci minuti, le query corrette in Testing Library: query corrette. Il confronto misurato fra librerie React è in librerie React 2026.

## FAQ

### Che differenza c'è fra vi.fn e vi.spyOn?

vi.fn crea una funzione nuova e vuota, utile per un callback da passare come prop. vi.spyOn avvolge una funzione esistente e la lascia funzionare, quindi serve quando il codice reale deve continuare a girare.

### Perché il mio mock di modulo non funziona?

Perché la factory non restituisce tutti gli export usati dal codice, oppure perché il percorso nel mock non coincide con quello dell'import. Il mock deve rispettare lo stesso percorso, relativo o con alias.

### Come faccio fallire la build sotto una soglia?

Configurando thresholds dentro coverage nel file di Vitest. Il comando esce con codice uno quando una metrica scende sotto il valore, e la pipeline si ferma.

### --changed come decide quali test eseguire?

Confronta i file modificati con un riferimento Git e cerca i test che li importano. Senza un riferimento esplicito usa l'ultimo commit, quindi in CI conviene passare HEAD~1 o il ramo di destinazione.

### Posso usare Vitest senza jsdom?

Sì, con environment node per il codice che non tocca il DOM. Serve un secondo progetto nella configurazione, perché un solo environment vale per tutti i file.
