
Il test runner nativo di Node.js: vale la pena lasciare Jest?
Guida pratica a node:test, il test runner integrato in Node.js: setup senza dipendenze, sintassi base, mock, coverage e cosa manca ancora rispetto a Jest.
Node.js ha un test runner integrato dalla versione 18 (stabile dalla 20), ma la stragrande maggioranza dei progetti continua a installare Jest o Vitest per default, spesso senza chiedersi se serva davvero. Il modulo node:test è maturato abbastanza da essere un’alternativa concreta per una buona fetta di progetti — non per tutti.
Setup: zero dipendenze
Non serve installare nulla. node:test fa parte del runtime:
// somma.test.js
import { test } from "node:test";
import assert from "node:assert/strict";
import { somma } from "./somma.js";
test("somma due numeri positivi", () => {
assert.strictEqual(somma(2, 3), 5);
});
test("somma con un negativo", () => {
assert.strictEqual(somma(5, -2), 3);
});
node --test
Nessun file di configurazione, nessun transform da configurare per ESM — node --test cerca automaticamente i file che matchano pattern comuni (*.test.js, *.spec.js, cartelle test/) ed esegue tutto in parallelo per default.
describe/it, hook e test asincroni
La sintassi è deliberatamente vicina a quella di Jest/Mocha, per rendere la migrazione meno dolorosa:
import { describe, it, before, after } from "node:test";
import assert from "node:assert/strict";
describe("UtenteRepository", () => {
let repo;
before(() => {
repo = new UtenteRepository();
});
after(async () => {
await repo.chiudi();
});
it("trova un utente per id", async () => {
const utente = await repo.trovaPerId(1);
assert.equal(utente.nome, "Diego");
});
it("lancia se l'id non esiste", async () => {
await assert.rejects(() => repo.trovaPerId(999), /non trovato/);
});
});
assert.rejects/assert.doesNotReject coprono i casi async che in Jest richiedono expect(...).rejects.toThrow() — l’API è più verbosa in alcuni punti, ma copre gli stessi scenari.
Mock integrati, senza librerie esterne
Dalla 20 in poi, node:test include un sistema di mock nativo (t.mock), che copre buona parte di quello per cui prima serviva jest.fn() o sinon:
import { test } from "node:test";
import assert from "node:assert/strict";
test("chiama il servizio email con i parametri giusti", (t) => {
const invia = t.mock.fn(() => true);
registraUtente({ email: "[email protected]" }, { inviaEmail: invia });
assert.strictEqual(invia.mock.callCount(), 1);
assert.deepEqual(invia.mock.calls[0].arguments, ["[email protected]"]);
});
t.mock.method() permette anche di sostituire temporaneamente un metodo su un oggetto esistente (l’equivalente di jest.spyOn), e viene ripristinato automaticamente alla fine del test.
💡 Consiglio
node --test --watch riesegue solo i test dei file modificati, con lo stesso spirito di jest --watch. Utile durante lo sviluppo, anche se il feedback su quali test sono stati re-triggerati è
più essenziale rispetto all’interfaccia di Jest.
Coverage senza pacchetti aggiuntivi
node --test --experimental-test-coverage
Il coverage è generato internamente (usa la strumentazione V8, non istanbul/nyc), stampato in console per default. Per un report HTML navigabile serve comunque configurare un reporter esterno — qui Jest, con --coverage pronto all’uso e integrazioni editor mature, resta più comodo.
Cosa manca ancora rispetto a Jest
- Snapshot testing:
node:testnon ha un equivalente nativo ditoMatchSnapshot(). Per progetti che si affidano molto agli snapshot (tipicamente componenti UI), è un’assenza che pesa. - Ambiente DOM/jsdom integrato: Jest configura
jsdomcon un flag; connode:testva aggiunto manualmente. Per testare componenti React/Vue è un lavoro in più che Jest (o Vitest) risparmiano. - Matcher espressivi:
assert.strictEqual/assert.deepEqualcoprono i casi comuni, ma l’ecosistema di matcher diexpect(toBeCloseTo,toContainEqual, matcher custom) è più ricco e leggibile per asserzioni complesse. - Ecosistema di plugin ed editor integration: Jest (e Vitest) hanno anni di plugin, estensioni VS Code e integrazioni CI mature.
node:testè più recente e la superficie di tooling attorno è ancora più piccola.
Quando ha senso migrare
Per librerie e pacchetti Node.js senza dipendenze da DOM — utility, client HTTP, logica di business pura, script CLI — node:test è spesso sufficiente e rimuove una dipendenza intera dal progetto, con avvio più veloce (nessun transform da caricare) e zero configurazione da mantenere. Per progetti frontend con componenti da renderizzare e snapshot da confrontare, o suite di test già grandi e stabili su Jest, la migrazione raramente vale il tempo che costa: il beneficio principale di node:test è partire senza dipendenze, non convertire quello che già funziona.
Per il resto dell’ecosistema Node lato sicurezza e manutenzione, vale la pena tenere d’occhio anche gli aggiornamenti descritti nell’articolo sulle security release di Node.js.

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