React
Testare un componente asincrono senza setTimeout
Un componente asincrono si testa con findBy e await: il test attende il DOM che cambia. Un setTimeout scritto a mano produce attese arbitrarie.
In sintesi
- findBy ripete la query fino al timeout: è l'attesa corretta, non un setTimeout.
- Lo stub va messo al confine, su fetch, non dentro il componente.
- Il timeout predefinito è 1.000 ms: in CI conviene alzarlo con configure, non con attese sparse.
Un componente asincrono si testa con findBy e await: il test attende che il DOM cambi. Un setTimeout scritto a mano introduce attese arbitrarie e test instabili.
La regola di base
In Testing Library findBy è waitFor più getBy: ripete la query ogni 50 ms fino a 1.000 ms. Se il dato arriva prima, il test prosegue subito; se non arriva, fallisce con la query che ha provato.
Il test che vale come modello
import { render, screen } from "@testing-library/react";
test("mostra il totale quando la risposta arriva", async () => {
vi.stubGlobal(
"fetch",
vi.fn(() => Promise.resolve({ ok: true, json: () => Promise.resolve({ total: 12 }) })),
);
render();
expect(await screen.findByText("12")).toBeInTheDocument();
});
Lo stub sostituisce fetch, cioè il confine del componente: la logica interna resta quella vera, la rete sparisce. L'assert guarda il testo che l'utente vede, non lo stato interno.
Tre errori frequenti
- Attesa fissa:
await new Promise((r) => setTimeout(r, 500))rende il test lento quando va bene e instabile quando va male. - Assert sincrono:
getByTextsubito dopo il render non trova nulla, perché la promessa non è ancora risolta. - Timer finti: con
vi.useFakeTimers()serveadvanceTimersin userEvent, altrimenti ogni clic resta fermo.
Installazione
npm i -D vitest jsdom @testing-library/react @testing-library/jest-dom
npx vitest run
Tutti i pacchetti sono MIT e girano su jsdom in locale: nessuna rete, nessun browser, stesso comando in CI.
Record vivi nel catalogo
Tre record asincroni su cui scrivere l'attesa: spinner, ai-chat-input e agent-dock. Per esplorare per categoria parti da 111.985 componenti.
La limitazione
Il test aspetta il DOM, non dimostra l'assenza di race: due richieste che tornano in ordine inverso possono passare lo stesso test. Il timeout predefinito di 1.000 ms, inoltre, su una CI lenta produce fallimenti che in locale non compaiono.
Dove continuare
Il modello di test è in Testing Library: cinque test; l'attesa con Suspense è in Suspense; il fetch senza librerie è in data fetching senza librerie; il confronto misurato fra librerie è in librerie React 2026.
Domande frequenti
Serve aspettare con setTimeout?
No. findBy ripete la query fino al timeout: il test prosegue appena il DOM cambia, senza attese fisse.
Perché compare un warning su act?
Perché lo stato cambia fuori da un await. Avvolgi l'azione in await userEvent o in await waitFor.
Meglio mockare fetch o il modulo?
fetch, cioè il confine. Mockando il modulo si testa il mock, non il componente.
Posso alzare il timeout?
Sì, con configure({ asyncUtilTimeout: 3000 }) una volta nel setup. Meglio un valore globale che attese sparse nei test.
Questi test provano che non ci sono race?
No. Provano che il DOM mostra il dato atteso in quell'ordine; le race si vedono con test ripetuti in browser reale.