Salta al contenuto principale

Unit Test JavaScript: Guida Completa 2026

Padroneggia i unit test JavaScript con la nostra guida 2026. Include Jest, Mocha, setup, mocking, CI e consigli per app Capacitor & Electron.

Unit Test JavaScript: Guida Completa 2026

Probabilmente ti trovi in una di queste due situazioni: o il tuo progetto JavaScript ha quasi zero test e ogni refactor sembra rischioso, o hai già dei test ma metà sono lenti, instabili e difficili da fidarsi.

La situazione peggiora con le Capacitor e Electron app. Una semplice feature può toccare logica di business condivisa, API del browser, plugin nativi, file locali, IPC e servizi remoti nello stesso flusso. Se testi questi componenti nel modo sbagliato, la tua suite diventerà un labirinto di dipendenze fake. Se li testi nel modo giusto, otterrai un feedback rapido sulla logica che si rompe.

I buoni unit test in JavaScript non iniziano con una sintassi di matcher ingegnosa. Iniziano con un confine disciplinato: testa la logica pura direttamente, isola gli effetti collaterali ed evita di scrivere test che si rompono non appena rinomini una funzione interna.

Indice dei contenuti

Scegliere il proprio framework di testing JavaScript

Un progetto JavaScript professionale ha bisogno di un vero test runner. Script ad hoc e controlli manuali in console non reggono quando più sviluppatori lavorano sullo stesso codebase. Servono test discovery, assertion, gestione dell'async, mock e un modo per eseguire tutto in modo coerente in locale e in CI.

Le linee guida attuali convergono su un piccolo insieme di opzioni mainstream. Jest, Mocha e Jasmine vengono indicati ripetutamente come i framework principali, con Jest spesso citato per la struttura dei test integrata, le assertion, il mocking e il supporto async in un unico pacchetto, come mostrato in questo lab di testing JavaScript di Pluralsight.

Una tabella comparativa dei framework di testing JavaScript più popolari, tra cui Jest, Mocha, Cypress e Playwright.

Perché un framework non è opzionale

Il primo errore che commettono i team è trattare gli unit test come un'attività secondaria. Questo porta spesso a nomi di file incoerenti, assertion personalizzate che nessuno ricorda e helper comprensibili da una sola persona.

Un framework ti offre un linguaggio condiviso:

  • Struttura dei test con describe e test o it
  • Assertion con matcher leggibili
  • Hooks per setup e teardown
  • Supporto async per promise e timer
  • Strumenti di mocking per dipendenze esterne

Se il tuo team necessita di una visione più ampia dell'automazione dei test oltre il livello unitario, Capgo offre una panoramica utile su test automatizzati nei workflow di rilascio app.

Jest vs Mocha a confronto

Jest e Mocha rappresentano due filosofie diverse.

Jest è l'opzione all-in-one. Include quasi tutto ciò che serve ai team fin dal primo giorno.
Mocha è più modulare. Ti fornisce un runner e ti lascia il compito di assemblare il resto dello stack.

Funzionalità Jest Mocha
Complessità setup Minore per la maggior parte dei team Maggiore, poiché si aggiungono librerie di assertion e mocking
Assertion Integrato Solitamente accoppiato a un'altra libreria
Mocking Integrato Solitamente accoppiato a un'altra libreria
Test asincroni Integrato e immediato Supportato, ma dipende dal setup circostante
Workflow di coverage Solitamente integrato nella stessa toolchain Spesso composto da più strumenti
Miglior scelta Nuovi progetti, team che cercano coerenza Stack legacy, team che preferiscono il controllo modulare

Regola pratica: Se il tuo team deve scegliere quale assertion e mocking library abbinare al runner, probabilmente ti serve Jest.

Cosa consiglio alla maggior parte dei team

Per la maggior parte dei progetti moderni, sceglierei Jest a meno che il codebase non abbia motivi validi per restare su Mocha. Questa raccomandazione diventa ancora più forte se l'applicazione include Capacitor o Electron, perché quei progetti hanno già troppi elementi in movimento. Ridurre la frammentazione dei tool di testing ripaga velocemente.

Mocha ha ancora senso in servizi Node.js datati o codebase storiche dove l'ecosistema è già consolidato. Ma per un ingegnere mid-level che deve configurare una suite robusta da zero, Jest solitamente riduce l'attrito invece di aumentarlo.

Una nota importante sull'ambito. Cypress e Playwright sono tool eccellenti, ma risolvono un problema diverso. Sono migliori per test a livello di browser ed end-to-end, non per il ciclo rapido di sviluppo dove dovrebbero risiedere i unit test JavaScript.

Setup del progetto e il tuo primo test

Un setup di testing pulito dovrebbe essere noioso. Se aggiungere il primo test sembra complicato, la suite probabilmente non rimarrà in salute.

Un uomo con gli occhiali che lavora a un progetto di programmazione su un laptop a una scrivania di legno.

Un semplice setup di Jest

Inizia con un progetto JavaScript che abbia già un package.json. Poi aggiungi Jest come dev dependency e configura uno script di test.

{
  "scripts": {
    "test": "jest"
  }
}

Per molti progetti è sufficiente. Potrai aggiungere altra configurazione in seguito se il tuo sistema di moduli, la transpilation o la struttura monorepo lo richiederanno.

Se stai sviluppando un'app Capacitor localmente e vuoi sistemare l'ambiente di dev prima di aggiungere test alla logica condivisa, la guida di Capgo per configurare un ambiente locale Capacitor è un ottimo punto di riferimento.

Scrivi il test prima del code

Il pattern test-first non è solo una preferenza personale. Le linee guida JavaScript del Consumer Financial Protection Bureau statunitense raccomandano esplicitamente di scrivere prima il test, organizzare i test con describe e it, e strutturare i controlli tramite expect(...) assertion nelle sue Guida ai unit test in JavaScript.

È fondamentale perché l'approccio test-first cambia il modo in cui progetti code. Le funzioni tendono a diventare più piccole, le dipendenze più visibili e gli effetti collaterali smettono di sporcare la logica che dovrebbe rimanere pura.

Ecco un esempio minimo:

// math.js
function addTax(amount, rate) {
  return amount + amount * rate;
}

module.exports = { addTax };
// math.test.js
const { addTax } = require('./math');

describe('addTax', () => {
  it('returns the amount with the tax applied', () => {
    expect(addTax(100, 0.2)).toBe(120);
  });
});

Usa sempre Arrange, Act, Assert

L'oggetto Arrange, Act, Assert mantiene i test leggibili, anche quando diventano più complessi.

  1. Arrange l'input e tutto il setup necessario.
  2. Act chiamando la funzione.
  3. Assert sull'esito.

Applicato a un helper di validazione:

function isSupportedPlatform(platform) {
  return ['ios', 'android', 'web', 'desktop'].includes(platform);
}

describe('isSupportedPlatform', () => {
  it('returns true for ios', () => {
    // Arrange
    const platform = 'ios';

    // Act
    const result = isSupportedPlatform(platform);

    // Assert
    expect(result).toBe(true);
  });
});

I test piccoli invecchiano bene. Un test dovrebbe solitamente rispondere a una sola domanda, non narrare un intero workflow.

Per i progetti Capacitor ed Electron, questa disciplina è ancora più importante perché la tua logica pura spesso risiede accanto a code native o desktop. Mantieni la business rule testabile senza il runtime della piattaforma, e il tuo primo test non sarà l'ultimo utile.

Padroneggiare Mock e Code asincroni

La maggior parte dei bug nelle code applicative non deriva dalla somma di due numeri. Derivano da code che interagiscono con l'esterno: richieste di rete, file, plugin API, timer, canali IPC o layer di storage.

È qui che il mocking aiuta. Ti dà il controllo sui confini, permettendo al test di concentrarsi sul processo decisionale della tua code.

Un diagramma su lavagna che illustra un'architettura a microservizi con API, data store, servizi esterni e flusso di dati event-driven.

Mocka i confini, non tutto

Le linee guida per test manutenibili enfatizzano copertura del singolo comportamento e un'unica assertion forte per test, e avverte inoltre che l'uso eccessivo di mock rende i test fragili e troppo legati ai dettagli di implementazione, come riassunto in questo articolo di TestRail sui unit test manutenibili.

Questo avvertimento è fondamentale in JavaScript. Spesso i team iniziano mockando ogni modulo importato e finiscono per testare se le funzioni chiamano altre funzioni nell'ordine "corretto", invece di testare il comportamento reale.

Target errato per un test basato su molti mock:

  • se l'helper A ha chiamato l'helper B
  • se il servizio C ha chiamato il serializer D
  • se una funzione privata interna è stata eseguita due volte

Obiettivo migliore:

  • cosa ha restituito la funzione
  • se ha gestito correttamente il fallimento di una dipendenza
  • se ha trasformato i dati nel formato previsto

Un pattern migliore per Capacitor ed Electron code

Nelle app mobile e desktop, preferisco un layer di wrapper attorno alle API native o di piattaforma. In questo modo, i unit test mockano il wrapper e non la piattaforma stessa.

Esempio di struttura:

// cameraGateway.js
async function getPhoto(cameraPlugin) {
  return cameraPlugin.getPhoto();
}

module.exports = { getPhoto };
// profilePhotoService.js
async function loadProfilePhoto(cameraGateway) {
  const photo = await cameraGateway.getPhoto();
  return { path: photo.path, ready: true };
}

module.exports = { loadProfilePhoto };
// profilePhotoService.test.js
const { loadProfilePhoto } = require('./profilePhotoService');

test('returns mapped photo data', async () => {
  const fakeCameraGateway = {
    getPhoto: jest.fn().mockResolvedValue({ path: '/tmp/pic.jpg' })
  };

  const result = await loadProfilePhoto(fakeCameraGateway);

  expect(result).toEqual({ path: '/tmp/pic.jpg', ready: true });
});

Questo pattern funziona anche con Electron. Avvolgi ipcRendererl'accesso ai file o le integrazioni shell dietro un thin adapter. I unit test colpiscono il service layer, non direttamente il runtime.

Per i team che testano la logica di release e i percorsi di aggiornamento in app Capacitor, Capgo offre una guida su come testare gli aggiornamenti OTA di Capacitor con scenari mock.

Una rapida panoramica può aiutare se il tuo team sta ancora normalizzando lo stile dei test async:

Testare i flussi async senza flakiness

Usa async/await nei test quando il code sotto test restituisce una promise. È più chiaro dei pattern basati su callback ed è più facile da debuggare.

async function fetchProfile(api) {
  const response = await api.getUser();
  return response.name;
}

test('returns the user name from the API response', async () => {
  const api = {
    getUser: jest.fn().mockResolvedValue({ name: 'Ava' })
  };

  const result = await fetchProfile(api);

  expect(result).toBe('Ava');
});

Testa anche il percorso di errore:

test('throws when the API request fails', async () => {
  const api = {
    getUser: jest.fn().mockRejectedValue(new Error('network failed'))
  };

  await expect(fetchProfile(api)).rejects.toThrow('network failed');
});

Testa sia il happy path che l'ugly path. In produzione, l'ugly path è solitamente quello che gli utenti ricordano meglio.

Strategie avanzate per test robusti

Una suite di test diventa utile quando rimane tale anche dopo i cambiamenti a code. È più difficile che scrivere un mucchio di test che passano.

Un diagramma che illustra le strategie per creare software robusto tramite una copertura di test completa e suite di test manutenibili.

Usa il testing split come budget

Una guida pratica raccomanda una 70/20/10 suddivisione tra test unitari, di integrazione ed end-to-end, con i test unitari che offrono il feedback più rapido e i fallimenti più stabili. La stessa guida suggerisce che una suite unitaria completa dovrebbe idealmente terminare in meno di 10 secondi, e i controlli pre-commit dovrebbero durare meno di 5 secondi, come indicato in questa Guida ai test con OpenReplay.

Lo considero uno strumento di budgeting, non una religione. Se la maggior parte del lavoro va nei test end-to-end, il team aspetterà troppo per il feedback. Se fai solo unit test, ti perderai i veri confini del sistema.

Per un'app Capacitor o Electron, un buon equilibrio è solitamente questo:

  • Unit test per la logica dei prezzi, le regole sui permessi, la serializzazione, l'idoneità agli update, i feature flag e le trasformazioni di stato
  • Integration test per adapter di storage, wrapper di plugin e contratti IPC
  • E2E test per alcuni flussi critici come login, flusso di acquisto, sincronizzazione o prompt di update

La coverage è una torcia, non un obiettivo

I report di coverage sono utili se aiutano a individuare rami non testati in logiche importanti. Diventano dannosi quando i team inseguono le percentuali di coverage fine a se stesse.

Un validatore di login con test accurati sui casi limite offre molto più valore di un file pieno di asserzioni banali. Questo vale soprattutto per code basati su input, come form, parser, logica delle date e controlli dei permessi. Se il tuo team sta alzando l'asticella della qualità sulle UI basate su validazione, questa guida su padroneggiare la validazione dei form frontend è il complemento ideale per una strategia di unit testing.

I test behavior-first sopravvivono ai refactor

Una suite affidabile deve permetterti di refactorare l'interno senza dover riscrivere metà dei test. Il modo più semplice per riuscirci è testare il comportamento osservabile invece dei dettagli di implementazione.

Casi d'uso che funzionano bene:

  • Condizioni limite come input vuoti, valori null, tipi non validi e stringhe troppo lunghe
  • Risultati di dominio come "ritorna errore per permessi mancanti"
  • Transizioni di stato come “segna l'update come pending dopo la validazione dei metadati”

Casi d'uso che spesso degradano:

  • ispezionare le chiamate degli helper interni
  • verificare la sequenza dei metodi privati
  • mockare ogni layer della catena di chiamata

Per i team che costruiscono processi di release disciplinati, l'articolo di Capgo su quality assurance delle app è utile perché collega il testing alla pipeline di release.

Test per CI, Capacitor e app Electron

Un test che gira solo sulla macchina di un developer non è una rete di sicurezza. È solo un'abitudine locale.

La CI trasforma i unit test JavaScript in infrastruttura di team. Ogni push, pull request o release branch può eseguire gli stessi comandi con le stesse aspettative. Questa coerenza è fondamentale per i progetti Capacitor ed Electron, dove l'environment drift causa errori sottili.

Rendi la CI il percorso di esecuzione predefinito

Al minimo, la tua CI dovrebbe installare le dipendenze ed eseguire la suite di unit test a ogni commit. Mantieni il comando identico allo sviluppo locale, quando possibile.

Un workflow base di GitHub Actions può essere semplice come questo:

name: test

on: [push, pull_request]

jobs:
  unit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npm test

È sufficiente per intercettare import errati, assertion fallite e assunzioni accidentali sulla piattaforma prima che arrivino nel branch main.

Per i team mobile che usano pipeline automatizzate, Capgo offre una guida pratica su configurare la CI/CD per le app Capacitor.

Testare le interazioni dei plugin Capacitor

Il modo sbagliato di testare Capacitor code è importare i plugin nativi direttamente in ogni servizio. Questo accoppia la tua suite di test al platform bridge.

Il pattern migliore è un'astrazione leggera:

// deviceStorage.js
async function saveFile(filesystem, path, data) {
  return filesystem.writeFile({ path, data });
}

module.exports = { saveFile };
// draftService.js
async function persistDraft(storage, draft) {
  await storage.save('draft.json', JSON.stringify(draft));
  return { saved: true };
}

module.exports = { persistDraft };
// draftService.test.js
const { persistDraft } = require('./draftService');

test('persists a serialized draft', async () => {
  const storage = {
    save: jest.fn().mockResolvedValue(undefined)
  };

  const result = await persistDraft(storage, { title: 'Hello' });

  expect(result).toEqual({ saved: true });
});

Lo stesso concetto vale per l'accesso alla camera, i prompt biometrici, la registrazione del push-token e lo stato della rete. Mantieni le chiamate ai plugin negli adapter. Testa la logica dell'app tramite interfacce che controlli.

Testare main, renderer e IPC di Electron code

Le app Electron hanno due punti di giunzione importanti: processo main code e processo renderer code. Non trascurarli nei test.

Un setup affidabile solitamente separa:

  • Unit test del Renderer per view model, stato, formattazione e logica UI
  • Unit test del processo Main per menu, operazioni file e lifecycle dell'app
  • Test del contratto IPC per la struttura dei messaggi e le risposte attese

Esempio di wrapper IPC:

// ipcGateway.js
function sendSettings(ipcRenderer, payload) {
  ipcRenderer.send('settings:update', payload);
}

module.exports = { sendSettings };
// ipcGateway.test.js
const { sendSettings } = require('./ipcGateway');

test('sends settings update over ipc', () => {
  const ipcRenderer = { send: jest.fn() };

  sendSettings(ipcRenderer, { theme: 'dark' });

  expect(ipcRenderer.send).toHaveBeenCalledWith('settings:update', { theme: 'dark' });
});

Se in seguito dovessi cambiare l'implementazione interna di un helper, questo test rimarrebbe valido perché verifica il comportamento che conta. Questo è lo standard da seguire per code desktop e mobile.

Domande frequenti sui unit test in JavaScript

Qual è la differenza tra unit, integration ed E2E test

Un unit test verifica una piccola porzione di logica in isolamento. Un test di integrazione verifica se alcuni componenti o servizi lavorano correttamente insieme. Un test end-to-end esegue un intero user journey attraverso l'applicazione in esecuzione.

Usa gli unit test per validare velocemente le business rule. Usa gli integration test per i punti di contatto come storage, plugin wrapper e IPC. Usa i test E2E con parsimonia, solo per i workflow critici.

Dovremmo puntare alla copertura completa?

No. La copertura totale può spingere i team verso test di scarso valore.

La coverage è utile quando rivela code rischiosi che nessuno ha testato. Non è utile se gli ingegneri aggiungono assertion superficiali solo per compiacere una dashboard. Se la tua suite è fragile, più coverage non la salverà.

Come aggiungere test a un codebase esistente

Inizia dove avvengono già i cambiamenti. Non bloccare il team annunciando un rewrite totale della strategia di test.

Una sequenza pratica è questa:

  • Proteggi prima i code attivi aggiungendo test ai moduli che tocchi durante lo sviluppo di feature o bug fix
  • Estrai la logica pura dai file difficili da testare, così le business rule possono essere testate senza il rumore di framework o runtime
  • Aggiungi dei wrapper (seam) attorno a plugin nativi, client di rete, chiamate al filesystem ed Electron IPC
  • Rifiuta i pattern fragili quando si introducono i mock. I consigli sulle best practice per il testing in JavaScript sono utilissimi in questa fase, poiché mettono in luce il problema dell'over-mocking e la fragilità dei test che ne deriva

L'obiettivo non è la completezza immediata, ma un miglioramento costante nei punti in cui le regressioni costano di più al team.


Se il tuo team rilascia Capacitor o Electron app e necessita di un processo di rilascio più pulito per le modifiche JavaScript, Capgo è un'opzione da considerare. Offre Live Update per app CapacitorJS ed Electron, con controlli di rollout e osservabilità, permettendo ai team di affiancare unit test solidi a un percorso più sicuro per distribuire modifiche ai web bundle senza attendere la revisione degli store per ogni fix.

Live update per le app Capacitor

Quando un bug del web layer è live, rilascia il fix tramite Capgo invece di aspettare giorni per l'approvazione degli store. Gli utenti ricevono l'update in background, mentre le modifiche native restano nel normale percorso di review.

Le ultime dal nostro blog

Capgo ti offre i migliori insight per creare un'app mobile davvero professionale.