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()oDate.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:
transitionerequestAnimationFrameche 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.
Record vivi nel catalogo
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.