React
Testare un form react-hook-form
Un form react-hook-form si testa dal DOM: digita con userEvent, clicca il submit e attendi l'errore con findByText. Non mockare la libreria.
In sintesi
- Il form si testa come lo usa una persona: digita, clicca, legge il messaggio.
- I messaggi di validazione arrivano dopo un tick: servono findByText e await.
- Mockare react-hook-form spegne proprio la logica che il test deve coprire.
Un form react-hook-form si testa dal DOM: digita con userEvent, clicca il submit e attendi il messaggio con findByText. Mockare la libreria toglie proprio la parte da verificare.
La regola di base
react-hook-form, con licenza MIT, registra gli input e valida al submit o al blur. Il messaggio d'errore compare dopo una microtask, quindi un getByText sincrono fallisce quasi sempre.
Il test che vale come modello
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
test("rifiuta l'email vuota", async () => {
const onSubmit = vi.fn();
render();
await userEvent.click(screen.getByRole("button", { name: "Crea account" }));
expect(await screen.findByText("Email obbligatoria")).toBeInTheDocument();
expect(onSubmit).not.toHaveBeenCalled();
});
Il test non tocca register né handleSubmit: clicca il pulsante reale e verifica due cose, la callback non chiamata e il messaggio visibile.
I quattro casi che bastano
- Campo vuoto: il submit non chiama la callback e mostra il messaggio obbligatorio.
- Formato errato: un'email senza chiocciola produce l'errore di formato, non quello di campo vuoto.
- Submit valido: la callback riceve i valori digitati, compresi quelli non toccati.
- Errore del server: la risposta 422 mostra il messaggio e lascia i dati nel form.
Setup
npm i -D vitest jsdom @testing-library/react @testing-library/user-event react-hook-form
npx vitest run --environment jsdom
Tutto MIT, tutto in locale: react-hook-form non fa chiamate di rete e il test non ha bisogno di un browser.
Record vivi nel catalogo
Tre form su cui provare i quattro casi: login-form, credit-card-form e otp-input. Per esplorare per categoria parti da 111.985 componenti.
La limitazione
userEvent digita carattere per carattere, quindi un campo lungo rallenta la suite: si usa paste quando il testo non è il punto del test. In jsdom, inoltre, autofill e gestori di password non esistono, e quei bug restano fuori.
Dove continuare
Il form con shadcn è in form con react-hook-form; controllato contro non controllato è in input controllati; il modello di test è in Testing Library: cinque test; il confronto misurato fra librerie è in librerie React 2026.
Domande frequenti
Devo mockare react-hook-form?
No. Il mock spegne validazione e stato, cioè la logica da verificare: si testa il form renderizzato.
Come invio il form in un test?
Con un clic sul pulsante di submit trovato per ruolo. submit() sull'elemento salta i gestori legati al clic.
Perché il messaggio di errore non compare?
Perché la validazione è asincrona: serve await findByText, non getByText subito dopo il clic.
Come testo un campo con maschera?
Digiti il valore finale e verifichi il testo mostrato; la sequenza dei tasti è un dettaglio dell'implementazione.
Il test copre anche la validazione lato server?
No, copre solo la risposta simulata. La regola vera resta sul server ed è quella che conta.