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.

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.