Salta al contenuto principale

Unit Test JavaScript: guida completa 2026

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

Illustrazione del blog Capgo: 'Unit test JavaScript: guida completa 2026'

Probabilmente ti trovi in una delle due situazioni: o il tuo progetto JavaScript non ha quasi test e ogni refactor sembra rischioso, oppure hai già dei test e metà sono lenti, fragili e stranamente poco affidabili.

E la situazione peggiora in 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 diventa un labirinto di dipendenze fake. Se li testi nel modo giusto, ottieni un feedback rapido sulla logica che si rompe.

Un buon lavoro di unit test in JavaScript non inizia con una sintassi di matcher ingegnosa. Inizia con un confine disciplinato: testare direttamente la logica pura, isolare gli effetti collaterali ed evitare di scrivere test che crollano al momento in cui si rinomina una funzione interna.

Indice dei contenuti

Scegliere il framework di test JavaScript

Un progetto JavaScript professionale ha bisogno di un vero test runner. Gli script ad hoc e i controlli manuali in console non scalano quando più ingegneri lavorano sulla stessa codebase. Servono discovery dei test, asserzioni, gestione asincrona, mock e un modo per eseguire tutto in modo coerente sia in sviluppo locale che in CI.

Le linee guida attuali convergono su un piccolo set di opzioni mainstream. Jest, Mocha e Jasmine vengono ripetutamente indicati come i framework principali, insieme a Jest spesso apprezzato per la struttura di test integrata, le asserzioni, il mocking e il supporto asincrono in un unico pacchetto, come mostrato in questo laboratorio di test JavaScript su Pluralsight.

Grafico comparativo dei framework di test JavaScript più popolari, tra cui Jest, Mocha, Cypress e Playwright.

Perché un framework non è opzionale

Il primo errore che i team commettono è considerare i test unitari un'attività secondaria. Di solito questo porta a un naming dei file incoerente, asserzioni custom che nessuno ricorda e helper che capisce solo una persona.

Un framework ti fornisce un linguaggio condiviso:

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

Se il tuo team ha bisogno di una visione più ampia dell'automazione dei test oltre i test unitari, 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à del setup Minore per la maggior parte dei team Maggiore, poiché solitamente si aggiungono librerie di assertion e mocking
Assertion Integrato Solitamente abbinata a un'altra libreria
Mocking Integrato Solitamente abbinata a un'altra libreria
Test asincroni Integrato e immediato Supportato, ma dipende di più dal setup circostante
Workflow di coverage Generalmente integrato nella stessa toolchain Spesso più frammentato
Consigliato Nuovi progetti, team che vogliono coerenza Stack legacy, team che vogliono controllo modulare

Regola pratica: Se il tuo team deve chiedere quale libreria di asserzioni e quale libreria di mocking abbinare al runner, probabilmente ti serve Jest.

Cosa consiglio per la maggior parte dei team

Per la maggior parte dei progetti moderni, sceglierei Jest a meno che il codebase non abbia già forti ragioni per restare su Mocha. Il consiglio si rafforza quando l'applicazione include Capacitor oppure Electron, perché quei progetti hanno già abbastanza variabili. Ridurre la proliferazione di tool di testing ripaga rapidamente.

Mocha ha ancora senso in servizi Node.js più vecchi o codebase longevi dove l'ecosistema circostante è già consolidato. Ma per un ingegnere di livello medio che imposta una suite robusta da zero, Jest di solito elimina più attrito di quanto ne crei.

Una nota importante sull'ambito. Cypress e Playwright sono strumenti eccellenti, ma risolvono un problema diverso. Sono più adatti per controlli a livello di browser e end-to-end, non per il fast inner loop dove dovrebbero stare gli unit test JavaScript.

Configurazione del progetto e primo test

Un setup di test pulito dovrebbe essere noioso. Se aggiungere il primo test sembra complicato, probabilmente la suite non resterà sana.

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

Una configurazione Jest semplice

Inizia con un progetto JavaScript che ha già un package.json. Poi aggiungi Jest come dipendenza di sviluppo e configura uno script di test.

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

Basta per molti progetti. Puoi aggiungere più configurazione in seguito se il tuo sistema di moduli, la transpilazione o la struttura del monorepo lo richiedono.

Se stai sviluppando localmente un'app Capacitor e vuoi mettere a posto l'ambiente di sviluppo prima di aggiungere test sulla logica condivisa, la guida di Capgo per configurare un ambiente locale Capacitor è un complemento pratico.

Scrivi il test prima del code

Il pattern test-first non è solo una preferenza personale. Le linee guida JavaScript dello U.S. Consumer Financial Protection Bureau raccomandano esplicitamente scrivere il test prima, organizzare i test con describe e it, e strutturare i controlli attorno a expect(...) assertion nel suo guida al unit testing in JavaScript.

È importante perché il test-first cambia il modo in cui progetti code. Le funzioni tendono a diventare più piccole, le dipendenze diventano più visibili e gli effetti collaterali smettono di infiltrarsi nella logica che dovrebbe restare 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 Arrange Act Assert ogni volta

Il Arrange, Act, Assert pattern mantiene i test leggibili, anche quando diventano più complessi.

  1. Prepara l'input e la configurazione necessaria.
  2. Agisci chiamando la funzione.
  3. Verifica 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 piccoli test invecchiano bene. Un test dovrebbe solitamente rispondere a una sola domanda, non narrare un intero workflow.

Per i progetti Capacitor ed Electron, questa disciplina conta ancora di più, perché la tua logica pura spesso convive con code di integrazione native o desktop. Mantieni la regola di business 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 nasce dal sommare due numeri. Nasce da code che interagisce con l'esterno: richieste di rete, file, API dei plugin, timer, canali IPC, layer di storage.

È qui che il mocking aiuta. Ti dà il controllo sul confine, così il test può 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 sottolineano copertura del singolo comportamento e un'unica assertion forte per test, avverte inoltre che l'abuso di mock rende i test fragili e troppo legati ai dettagli di implementazione, come riassunto in questo articolo di TestRail sugli 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 con 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. Poi i test unitari mockano il wrapper, non la piattaforma stessa.

Struttura di esempio:

// 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 });
});

Quello schema funziona anche per Electron. Avvolgi ipcRenderer, accesso ai file o integrazioni con shell dietro un sottile adapter. I test unitari colpiscono il layer di servizio, non il runtime direttamente.

Per i team che testano la logica di rilascio e i percorsi di aggiornamento nelle app Capacitor, Capgo ha una guida pertinente su il test degli aggiornamenti OTA di Capacitor con scenari mock.

Un rapido esempio aiuta se il tuo team sta ancora standardizzando lo stile di test asincroni:

Testare i flussi async senza flakiness

Usa async/await nei test quando il code in esame restituisce una promise. È più chiaro dei pattern basati su callback e 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 percorso felice che quello scomodo. In produzione, è di solito il secondo che gli utenti ricordano.

Strategie avanzate per test robusti

Una suite di test diventa utile quando resta utile anche dopo che il code cambia. È più difficile che scrivere un mucchio di test che passano.

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

Usa il testing split come un budget

Una guida pratica consiglia una 70/20/10 suddivisione tra test unitari, di integrazione e end-to-end, con i test unitari che offrono il feedback più rapido e i fallimenti più stabili. Le stesse linee guida indicano che una suite unitaria completa dovrebbe idealmente terminare in meno di 10 secondi, e i controlli pre-commit dovrebbero restare sotto i 5 secondi, come indicato in questa Guida ai test di OpenReplay.

La considero uno strumento di budgeting, non una religione. Se la maggior parte del tuo sforzo va nei test end-to-end, il tuo team aspetterà troppo a lungo per il feedback. Se tutto è solo unitario, perderai i confini reali del sistema.

Per un'app Capacitor o Electron, un equilibrio sano di solito è così:

  • Test unitari per la logica di pricing, le regole di autorizzazione, la serializzazione, l'idoneità agli aggiornamenti, i feature flag e le trasformazioni di stato
  • Test di integrazione per gli adapter di storage, i wrapper dei plugin e i contratti IPC
  • Test E2E per alcuni percorsi critici come login, flusso di acquisto, sync o prompt di aggiornamento

La coverage è una torcia, non un obiettivo

I report di coverage sono utili quando ti aiutano a individuare rami non testati in logiche importanti. Diventano dannosi quando i team inseguono le percentuali di coverage per il solo fatto di farlo.

Un validatore di login con test attenti ai casi limite offre più valore di un file coperto pieno di asserzioni banali. Questo vale soprattutto per il code ricco di input come form, parser, logica delle date e controlli dei permessi. Se il tuo team sta rafforzando la qualità attorno a UI con molta validazione, questa guida su la padronanza della validazione dei form frontend è un buon complemento alla strategia di test a livello unitario.

I test behavior-first sopravvivono ai refactor

Una suite affidabile dovrebbe permetterti di rifattorizzare gli interni senza riscrivere metà dei test. Il modo più semplice per arrivarci è asserire il comportamento osservabile anziché i dettagli di implementazione.

Casi d'uso che reggono bene:

  • Condizioni al limite come input vuoti, valori null-like, tipi non validi e stringhe troppo lunghe
  • Risultati di dominio ad esempio “ritorna denied per permesso mancante”
  • Transizioni di stato ad esempio “segna l'update come pending dopo la validazione dei metadati di download”

Casi d'uso che spesso invecchiano male:

  • ispezionare le chiamate a helper interni
  • asserire la sequenza di metodi privati
  • mockare ogni layer nella call chain

Per i team che costruiscono processi di release disciplinati, l'articolo di Capgo su garanzia di qualità dell'app è utile perché collega il lavoro di testing alla pipeline di rilascio più ampia.

Test per CI, Capacitor ed app Electron

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

La CI trasforma il lavoro sui unit test JavaScript in infrastruttura di team. Ogni push, pull request o branch di rilascio può eseguire gli stessi comandi con le stesse aspettative. Questa coerenza conta ancora di più per i progetti Capacitor ed Electron, dove la deriva dell'ambiente causa fallimenti 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 set di modifiche. Mantieni il comando identico a quello dello sviluppo locale, quando possibile.

Un workflow di base di GitHub Actions può essere ridotto a 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 rotti, assertion fallite e assunzioni accidentali sulla piattaforma prima che finiscano nel main.

Per i team mobile che rilasciano tramite pipeline automatizzate, Capgo ha una guida pratica su configurare la CI/CD per app Capacitor.

Testare le interazioni dei plugin Capacitor

Il modo sbagliato di fare unit test su Capacitor code è importare i plugin nativi direttamente in ogni service. Questo accoppia la tua suite di test al bridge della piattaforma.

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 vale per l'accesso alla fotocamera, i prompt biometrici, la registrazione del push token e lo stato della rete. Tieni 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 sutura importanti: processo main code e processo renderer code. Non confonderli nei test.

Un setup affidabile di solito separa:

  • Unit test del renderer per view model, stato, formattazione e logica di business lato UI
  • Unit test del processo principale per menu, operazioni sui file e decisioni sul ciclo di vita dell'app
  • Test dei contratti IPC per la forma 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 cambi l'implementazione interna da un helper a un altro, questo test resta valido perché verifica il comportamento che conta. È lo standard che vuoi per desktop e mobile code.

Domande frequenti sui unit test in JavaScript

Qual è la differenza tra test unit, integration ed E2E?

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 user journey attraverso l'applicazione in esecuzione.

Usa gli unit test per avere rapidamente fiducia nelle business rule. Usa gli integration test per i punti di integrazione come storage, wrapper dei plugin e IPC. Usa gli E2E test con parsimonia, per i workflow che causerebbero seri problemi se si rompessero.

Dovremmo puntare alla copertura totale?

No. La copertura totale può spingere i team verso test a basso valore.

La copertura è utile quando rivela code a rischio che nessuno ha testato. Non è utile quando gli ingegneri aggiungono asserzioni superficiali solo per soddisfare la dashboard. Se la tua suite è fragile, più copertura non la salverà.

Come aggiungere test a un codebase esistente

Parti da dove avvengono già i cambiamenti. Non bloccare il team e annunciare un'enorme riscrittura della strategia di test.

Una sequenza pratica è questa:

  • Proteggi prima i code attivi aggiungendo test ai moduli che tocchi mentre lavori a feature o correggi bug
  • Estrai la logica pura da file difficili da testare, così le regole di business si possono testare senza rumore di framework o runtime
  • Aggiungi wrapper per i seam attorno a plugin nativi, client di rete, chiamate al filesystem e IPC di Electron
  • Rifiuta i pattern fragili quando introduci i mock. Le indicazioni da best practice per il testing JavaScript sono particolarmente utili qui, perché evidenziano il problema spesso trascurato dell'over-mocking e dei test fragili che ne derivano

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


Se il tuo team rilascia Capacitor oppure Electron app e richiede un processo di rilascio più pulito per le modifiche JavaScript, Capgo è un'opzione da valutare. Offre live update per app CapacitorJS ed Electron, con controlli di rollout e osservabilità, così i team possono abbinare test unitari solidi a un percorso più sicuro per pubblicare modifiche al web bundle senza attendere la revisione dello store per ogni fix.

Live updates per le app Capacitor

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

Ultimi articoli dal blog

Capgo ti offre gli insight migliori che ti servono per creare un'app mobile davvero professionale.