
Vitest: cos'è, come si installa e come usarlo con React 19
Guida pratica a Vitest: installazione, configurazione, come testare componenti React 19 (incluso useActionState), mock con vi, coverage e confronto con Jest e node:test.
Vitest è il test runner sviluppato dal team di Vite, pensato per condividere la stessa pipeline di trasformazione del bundler: stesso vite.config.ts, stessi plugin, stessi alias, niente configurazione parallela da mantenere. L’API è deliberatamente compatibile con quella di Jest (describe, it, expect), quindi la migrazione da un progetto Jest esistente è quasi sempre un cambio di importazioni, non una riscrittura.
Installazione
npm install -D vitest
Per progetti React serve anche un ambiente DOM e le utility di testing:
npm install -D jsdom @testing-library/react @testing-library/jest-dom @testing-library/user-event
La configurazione va nello stesso file di Vite (o in un vitest.config.ts separato, se preferisci tenerla isolata dal build):
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
test: {
environment: "jsdom",
globals: true,
setupFiles: "./vitest.setup.ts",
},
});
// vitest.setup.ts
import "@testing-library/jest-dom/vitest";
globals: true evita di dover importare describe/it/expect in ogni file (stesso comportamento a cui è abituato chi arriva da Jest).
Un primo test
// somma.test.js
import { expect, test } from "vitest";
import { somma } from "./somma.js";
test("somma due numeri positivi", () => {
expect(somma(2, 3)).toBe(5);
});
npx vitest
Di default parte in watch mode e riesegue solo i test toccati dai file modificati, sfruttando lo stesso grafo di dipendenze che Vite usa per l’HMR — è la differenza pratica più evidente rispetto a Jest: qui non c’è un transform separato da ricalcolare, i file già trasformati restano in cache.
Testare un componente React 19
Esempio con useActionState, l’hook introdotto in React 19 per gestire lo stato derivato da una form action:
// LikeButton.tsx
import { useActionState } from "react";
async function likeAction(prevState: number) {
await new Promise((resolve) => setTimeout(resolve, 50));
return prevState + 1;
}
export function LikeButton() {
const [likes, formAction, isPending] = useActionState(likeAction, 0);
return (
<form action={formAction}>
<button type="submit" disabled={isPending}>
{isPending ? "..." : `Mi piace (${likes})`}
</button>
</form>
);
}
// LikeButton.test.tsx
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { expect, test } from "vitest";
import { LikeButton } from "./LikeButton";
test("incrementa i like dopo il submit", async () => {
const user = userEvent.setup();
render(<LikeButton />);
await user.click(screen.getByRole("button"));
expect(await screen.findByText("Mi piace (1)")).toBeInTheDocument();
});
findByText è necessario perché formAction è asincrona: il bottone passa prima per lo stato isPending, e serve aspettare il re-render finale invece di leggere subito il DOM con getByText. Per approfondire le altre novità dell’hook system in React 19 (incluso use()), c’è l’articolo su use vs useContext.
Mock con vi
import { vi, test, expect } from "vitest";
import { registraUtente } from "./registraUtente.js";
test("chiama il servizio email con i parametri giusti", () => {
const inviaEmail = vi.fn(() => true);
registraUtente({ email: "[email protected]" }, { inviaEmail });
expect(inviaEmail).toHaveBeenCalledWith("[email protected]");
});
vi.spyOn() sostituisce temporaneamente un metodo su un oggetto esistente, vi.mock() sostituisce un intero modulo (utile per isolare chiamate fetch o client HTTP) — stessa superficie concettuale di jest.fn/jest.spyOn/jest.mock, nomi diversi.
💡 Consiglio
npx vitest --ui apre una dashboard nel browser con l’elenco dei test, i risultati e un timeline
dei singoli assert — utile per debuggare una suite grande senza leggere output di terminale.
Richiede il pacchetto separato @vitest/ui.
Coverage
npm install -D @vitest/coverage-v8
npx vitest run --coverage
Il coverage usa la stessa strumentazione V8 di Node, con report testuale in console e HTML navigabile generato in coverage/ — pronto all’uso, senza configurare istanbul/nyc a parte.
Vitest vs Jest vs node:test
| Vitest | Jest | node:test |
|
|---|---|---|---|
| Config condivisa col build | Sì (stesso vite.config.ts) |
No (config separata) | N/A |
| TypeScript/JSX senza setup extra | Sì | Serve ts-jest/babel-jest |
Serve loader esterno |
| Ambiente DOM (jsdom) | Sì, con un flag | Sì, con un flag | Manuale |
| Snapshot testing | Sì | Sì | No |
| Dipendenze | Vite + eventuali plugin | Nessuna extra (ma ecosistema Babel spesso presente) | Zero |
| Watch mode basato su | Grafo di dipendenze Vite (HMR) | File system polling | File system |
Jest resta la scelta più matura per progetti non basati su Vite, con anni di plugin ed estensioni editor. node:test è l’opzione a costo zero per librerie senza dipendenze DOM, ma manca di snapshot testing e ambiente jsdom integrato — l’ho confrontato più in dettaglio nell’articolo su node:test vs Jest. Vitest si posiziona nel mezzo: per progetti già su Vite, Astro, Next.js (in modalità Vite) o qualsiasi setup React/TypeScript moderno, è quasi sempre la scelta con meno attrito, perché riusa configurazione e velocità del bundler che il progetto ha già.
Quando ha senso sceglierlo
Se il progetto usa già Vite (o un framework basato su Vite), Vitest evita di mantenere due pipeline di build separate e parte più veloce grazie al transform condiviso. Per progetti legacy su Webpack con una suite Jest grande e stabile, la migrazione raramente vale il tempo che costa — a meno che la lentezza dei test in CI sia già un problema concreto da risolvere.

Co-Fondatore & CTO presso PAPION. Senior full-stack engineer specializzato in React, TypeScript, Node.js e sicurezza applicativa.