React

Trovare i test instabili prima della CI

Un test instabile si trova con ordine casuale: quello che passa solo al secondo tentativo ha una causa. Il retry la nasconde, non la toglie.

In sintesi

  • Il retry rende verde la pipeline e lascia il bug: serve solo a misurare, non a correggere.
  • L'ordine casuale espone lo stato condiviso fra file, la causa più comune.
  • Un test instabile che non si riproduce in locale dipende da tempo, rete o ordine di esecuzione.

Un test instabile si trova ripetendo la suite con ordine casuale: quello che passa solo al secondo tentativo ha una causa, quasi sempre stato condiviso, tempo reale o rete.

La regola di base

Vitest, con licenza MIT, offre due opzioni che servono a questo: --sequence.shuffle cambia l'ordine dei file e --retry ripete un test fallito. La prima espone il problema, la seconda lo misura.

Il comando che li espone

npx vitest run --sequence.shuffle --retry=2 --reporter=verbose

Se la suite è verde solo con i retry, il numero di tentativi è la misura del problema: in main il valore accettabile è zero, perché ogni retry è un test che a volte mente.

Le cinque cause

  • Stato condiviso: un modulo importato che conserva valori fra un file e l'altro; si vede solo cambiando l'ordine.
  • Tempo: new Date() o Date.now() letti dentro il test o nel componente.
  • Casualità: Math.random(), crypto.randomUUID(), id generati al mount.
  • Rete e filesystem: una chiamata vera che a volte risponde e a volte no.
  • Animazioni: transition e requestAnimationFrame che in CI non finiscono mai.

Ripetere la suite

for i in 1 2 3 4 5; do
  npx vitest run --sequence.shuffle > "run-$i.log" 2>&1 || echo "fail $i"
done

Cinque giri con semi diversi bastano a far emergere la dipendenza dall'ordine. Il seme va scritto nel log: senza, il fallimento non si riproduce e la diagnosi ricomincia da zero.

Tre record che dipendono da tempo o animazione: image-auto-slider, sonner e spinner. Per esplorare per categoria parti da 111.985 componenti.

La limitazione

L'ordine casuale non trova i test che dipendono dall'ora del giorno o da un servizio esterno lento, perché quelli falliscono solo in certe finestre. E il retry in CI, senza un report, nasconde il conteggio: il flake resta e nessuno lo vede.

Dove continuare

Il modello di test è in Testing Library: cinque test; il doppio mount che confonde i test è in StrictMode; il profilo di rendering è in React Profiler; il confronto misurato fra librerie è in librerie React 2026.

Domande frequenti

Un retry in CI risolve il problema?

No, lo nasconde. Serve a misurare quanto è frequente il fallimento, non a correggerne la causa.

Come riproduco un test instabile?

Con lo stesso seme di ordine e molte ripetizioni: senza seme il fallimento non torna e la diagnosi si blocca.

Perché fallisce solo in CI?

Perché la CI è più lenta e ha un fuso orario diverso: le attese fisse e le date locali cedono lì e non in locale.

Il test dipende dall'ordine: cosa guardo?

Lo stato a livello di modulo: un array, una cache o un contatore fuori dalla funzione di test.

Meglio togliere il test o correggerlo?

Correggerlo. Toglierlo elimina la copertura e lascia il bug che il test aveva trovato.