Cómo probar formularios en React usando React Hook Form y React Testing Library

Gabriel Jiménez | Hace 4 días

En este artículo, aprenderemos porqué es importante probar un formulario en React. Para crear un formulario utilizaremos la librería React Hook Form, para facilitar manejar todo lo relacionado a un formulario como: validaciones, sincronización de estados. Por último, desarrollaremos un ejercicio práctico para aprender como probarlo usando React Testing Library.


Al final del artículo serás capas de entender la importancia de probar un formulario, las ventajas de usar librerías para manejar el estado de un formulario y lo más importante, a realizar pruebas sin miedo a romper funcionalidades ya desarrolladas. 


Por qué probar formularios en React

Los formularios son esenciales para comunicar los servicios de una aplicación con el usuario. A que me refiero con esto, si quiero aprender ingles en Duolingo, tengo que registrarme usando su formulario.


Cuando llenamos un formulario, usualmente tenemos el flujo principal o también llamado Happy Path, donde esperamos que todo salga bien. Por otro lado, existen los flujos alternos, como su nombre lo indica son flujos que distintos que pueden ocurrir dentro del formulario.


Resumiendo, debemos enfocarnos en probar que la comunicación del frontend y el backend se realice correctamente.


Si quieres aprender más sobre este tema, te recomiendo mi artículo: Cómo probar formularios en React con React Testing Library (guía completa)


Cómo probar interacciones del usuario usando React Testing Library en un formulario con React Hook Form

React Testing Library permite simular gran parte de lo que un usuario haría en un formulario, como escribir en campo, seleccionar una opción de una lista, hacer click en un checkbox. Por otro lado, React Hook Form nos permite crear un formulario para manejar los errores, validaciones, sincronización de data y más, de una forma más escalable.


Si quieres aprender más sobre React Hook Form y React Testing Library, te recomiendo estos artículos:


Tu primer formulario en React con React Hook Form

Qué es React Testing Library y cómo funciona con Jest


Seleccionar un campo de tipo texto


import { useForm } from "react-hook-form";

export function SimpleForm({ onSubmit }) {
  // Inicializamos React Hook Form con un valor por defecto
  const { register, handleSubmit } = useForm({
    defaultValues: { nombre: "" }
  });

  return (
    // handleSubmit permite procesar los datos del formulario
    <form onSubmit={handleSubmit(onSubmit)}>
      {/* Campo de texto sencillo */}
      <input
        type="text"
        placeholder="Nombre"
        {...register("nombre")}
      />

      {/* Botón para enviar el formulario */}
      <button type="submit">Guardar</button>
    </form>
  );
}

import { render, screen, fireEvent } from "@testing-library/react";
import { SimpleForm } from "./SimpleForm";

describe("SimpleForm", () => {
  it("permite escribir en el campo de texto", () => {
    // Renderizamos el componente para interactuar con él
    render(<SimpleForm onSubmit={() => {}} />);

    // Obtenemos el input usando su placeholder
    const input = screen.getByPlaceholderText("Nombre");

    // Simulamos que el usuario escribe "Gabriel"
    fireEvent.change(input, { target: { value: "Gabriel" } });

    // Validamos que el input tenga el valor esperado
    expect(input).toHaveValue("Gabriel");
  });
});

Seleccionar un campo de tipo date


import { render, screen, fireEvent } from "@testing-library/react";
import { SimpleForm } from "./SimpleForm";

describe("SimpleForm", () => {
  it("permite escribir en el campo de texto", () => {
    // Renderizamos el componente para interactuar con él
    render(<SimpleForm onSubmit={() => {}} />);

    // Obtenemos el input usando su placeholder
    const input = screen.getByPlaceholderText("Nombre");

    // Simulamos que el usuario escribe "Gabriel"
    fireEvent.change(input, { target: { value: "Gabriel" } });

    // Validamos que el input tenga el valor esperado
    expect(input).toHaveValue("Gabriel");
  });
});

import { render, screen, fireEvent } from "@testing-library/react";
import { SimpleFormDate } from "./SimpleFormDate";

describe("SimpleFormDate", () => {
  it("permite seleccionar una fecha", () => {
    // Renderizamos el formulario para interactuar con él
    render(<SimpleFormDate onSubmit={() => {}} />);

    // Obtenemos el input de tipo date
    const input = screen.getByPlaceholderText("Fecha");

    // Simulamos seleccionar una fecha
    fireEvent.change(input, { target: { value: "2025-11-19" } });

    // Verificamos que el valor haya cambiado correctamente
    expect(input).toHaveValue("2025-11-19");
  });
});

Trabajar con un campo seleccionable


import { useForm } from "react-hook-form";

export function SimpleFormSelect({ onSubmit }) {
  // Inicializamos el formulario con un valor vacío
  const { register, handleSubmit } = useForm({
    defaultValues: { pais: "" }
  });

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      {/* Campo select con opciones */}
      <select {...register("pais")} data-testid="select-pais">
        <option value="">Selecciona un país</option>
        <option value="mx">México</option>
        <option value="ar">Argentina</option>
        <option value="es">España</option>
      </select>

      <button type="submit">Guardar</button>
    </form>
  );
}

import { render, screen, fireEvent } from "@testing-library/react";
import { SimpleFormSelect } from "./SimpleFormSelect";

describe("SimpleFormSelect", () => {
  it("permite seleccionar una opción del select", () => {
    // Renderizamos el componente
    render(<SimpleFormSelect onSubmit={() => {}} />);

    // Obtenemos el select usando un data-testid
    const select = screen.getByTestId("select-pais");

    // Simulamos seleccionar "Argentina"
    fireEvent.change(select, { target: { value: "ar" } });

    // Validamos que el valor haya cambiado correctamente
    expect(select).toHaveValue("ar");
  });
});

Clic en un checkbox


import { useForm } from "react-hook-form";

export function SimpleFormCheckbox({ onSubmit }) {
  // Inicializamos el formulario con el checkbox en false
  const { register, handleSubmit } = useForm({
    defaultValues: { acepto: false }
  });

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      {/* Checkbox simple */}
      <input
        type="checkbox"
        {...register("acepto")}
        data-testid="checkbox-acepto"
      />

      <button type="submit">Guardar</button>
    </form>
  );
}

import { render, screen, fireEvent } from "@testing-library/react";
import { SimpleFormCheckbox } from "./SimpleFormCheckbox";

describe("SimpleFormCheckbox", () => {
  it("permite marcar y desmarcar el checkbox", () => {
    // Renderizamos el componente
    render(<SimpleFormCheckbox onSubmit={() => {}} />);

    // Obtenemos el checkbox
    const checkbox = screen.getByTestId("checkbox-acepto");

    // Simulamos marcar el checkbox
    fireEvent.click(checkbox);

    // Validamos que esté marcado
    expect(checkbox).toBeChecked();

    // Simulamos desmarcarlo
    fireEvent.click(checkbox);

    // Validamos que esté desmarcado
    expect(checkbox).not.toBeChecked();
  });
});

Seleccionar una opción de un radiobutton


import { useForm } from "react-hook-form";

export function SimpleFormRadio({ onSubmit }) {
  // Inicializamos el formulario con el valor vacío
  const { register, handleSubmit } = useForm({
    defaultValues: { genero: "" }
  });

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      {/* Radio para seleccionar género */}
      <label>
        <input
          type="radio"
          value="masculino"
          {...register("genero")}
          data-testid="radio-masculino"
        />
        Masculino
      </label>

      <label>
        <input
          type="radio"
          value="femenino"
          {...register("genero")}
          data-testid="radio-femenino"
        />
        Femenino
      </label>

      <button type="submit">Guardar</button>
    </form>
  );
}

import { render, screen, fireEvent } from "@testing-library/react";
import { SimpleFormRadio } from "./SimpleFormRadio";

describe("SimpleFormRadio", () => {
  it("permite seleccionar una opción de radio", () => {
    // Renderizamos el componente
    render(<SimpleFormRadio onSubmit={() => {}} />);

    // Obtenemos ambos radios
    const masculino = screen.getByTestId("radio-masculino");
    const femenino  = screen.getByTestId("radio-femenino");

    // Marcamos el radio Masculino
    fireEvent.click(masculino);

    // Validamos que ese esté seleccionado
    expect(masculino).toBeChecked();

    // Validamos que el otro NO esté seleccionado
    expect(femenino).not.toBeChecked();

    // Ahora seleccionamos Femenino
    fireEvent.click(femenino);

    // Validamos que cambió correctamente
    expect(femenino).toBeChecked();
    expect(masculino).not.toBeChecked();
  });
});

Escribir en un campo de tipo textarea


import { useForm } from "react-hook-form";

export function SimpleFormTextarea({ onSubmit }) {
  // Inicializamos el formulario con un valor vacío
  const { register, handleSubmit } = useForm({
    defaultValues: { comentario: "" }
  });

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      {/* Campo textarea */}
      <textarea
        placeholder="Comentario"
        {...register("comentario")}
      />

      <button type="submit">Guardar</button>
    </form>
  );
}

import { render, screen, fireEvent } from "@testing-library/react";
import { SimpleFormTextarea } from "./SimpleFormTextarea";

describe("SimpleFormTextarea", () => {
  it("permite escribir en el textarea", () => {
    // Renderizamos el componente para interactuar con él
    render(<SimpleFormTextarea onSubmit={() => {}} />);

    // Obtenemos el textarea usando su placeholder
    const textarea = screen.getByPlaceholderText("Comentario");

    // Simulamos escribir texto dentro del textarea
    fireEvent.change(textarea, { target: { value: "Este es un comentario" } });

    // Validamos que el valor haya cambiado correctamente
    expect(textarea).toHaveValue("Este es un comentario");
  });
});

Cómo validar en envío del formulario al backend

Cuando enviamos información al backend, esta tiene un formato definido. Nuestra tarea es asegurarnos que el formato se cumpla. Si el formato esta correcto y se procesa bien por el backend, nos devolverá una respuesta exitosa, mientras si ocurre algo mal, devuelve una respuesta fallida.


Confirmar si la información se ha enviado correctamente


import { useForm } from "react-hook-form";

export function SimpleBackendForm({ onSubmit }) {
  // Inicializamos el formulario con valores vacíos
  const { register, handleSubmit } = useForm({
    defaultValues: {
      nombre: "",
      email: "",
      mensaje: ""
    }
  });

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      {/* Campo: nombre */}
      <input
        type="text"
        placeholder="Nombre"
        {...register("nombre")}
      />

      {/* Campo: email */}
      <input
        type="email"
        placeholder="Email"
        {...register("email")}
      />

      {/* Campo: mensaje */}
      <textarea
        placeholder="Mensaje"
        {...register("mensaje")}
      />

      <button type="submit">Enviar</button>
    </form>
  );
}

import { render, screen, fireEvent } from "@testing-library/react";
import { SimpleBackendForm } from "./SimpleBackendForm";

describe("SimpleBackendForm", () => {
  it("envía los datos correctos usando onSubmit", () => {
    // Mock de la función que simula el envío al backend
    const mockOnSubmit = jest.fn();

    // Renderizamos el formulario con la función mock
    render(<SimpleBackendForm onSubmit={mockOnSubmit} />);

    // Obtenemos los campos
    const nombreInput = screen.getByPlaceholderText("Nombre");
    const emailInput = screen.getByPlaceholderText("Email");
    const mensajeTextarea = screen.getByPlaceholderText("Mensaje");
    const submitButton = screen.getByText("Enviar");

    // Simulamos que el usuario escribe datos
    fireEvent.change(nombreInput, { target: { value: "Gabriel" } });
    fireEvent.change(emailInput, { target: { value: "[email protected]" } });
    fireEvent.change(mensajeTextarea, {
      target: { value: "Hola, este es un mensaje" }
    });

    // Simulamos enviar el formulario
    fireEvent.click(submitButton);

    // Validamos que onSubmit fue llamado con los datos correctos
    expect(mockOnSubmit).toHaveBeenCalledWith({
      nombre: "Gabriel",
      email: "[email protected]",
      mensaje: "Hola, este es un mensaje"
    });
  });
});

Validar respuestas exitosas devueltas por el backend


import { useForm } from "react-hook-form";
import { useState } from "react";

export function SimpleBackendFormSuccess({ onSubmit }) {
  const [successMessage, setSuccessMessage] = useState("");

  const { register, handleSubmit } = useForm({
    defaultValues: {
      nombre: "",
      email: "",
      mensaje: ""
    }
  });

  const handleFormSubmit = async (data) => {
    const response = await onSubmit(data);

    // Mostramos un mensaje genérico si es el backend es exitoso.
    setSuccessMessage("Creado correctamente");
  };

  return (
    <form onSubmit={handleSubmit(handleFormSubmit)}>
      <input type="text" placeholder="Nombre" {...register("nombre")} />
      <input type="email" placeholder="Email" {...register("email")} />
      <textarea placeholder="Mensaje" {...register("mensaje")} />

      <button type="submit">Enviar</button>

      {successMessage && <p>{successMessage}</p>}
    </form>
  );
}

import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import { SimpleBackendFormSuccess } from "./SimpleBackendFormSuccess";

describe("SimpleBackendFormSuccess", () => {
  it("muestra 'Creado correctamente' el backend es exitoso", async () => {
    // Simulamos que el backend responde exitosamente
    const mockOnSubmit = jest.fn().mockResolvedValue(true);

    render(<SimpleBackendFormSuccess onSubmit={mockOnSubmit} />);

    // Llenamos los campos del formulario
    fireEvent.change(screen.getByPlaceholderText("Nombre"), {
      target: { value: "Gabriel" }
    });
    fireEvent.change(screen.getByPlaceholderText("Email"), {
      target: { value: "[email protected]" }
    });
    fireEvent.change(screen.getByPlaceholderText("Mensaje"), {
      target: { value: "Hola" }
    });

    // Enviamos el formulario
    fireEvent.click(screen.getByText("Enviar"));

    // Validamos que renderizó el mensaje genérico
    await waitFor(() => {
      expect(screen.getByText("Creado correctamente")).toBeInTheDocument();
    });
  });
});

Mostrar mensajes de errores de respuestas fallidas enviadas por el backend


import { useForm } from "react-hook-form";
import { useState } from "react";

export function SimpleBackendFormError({ onSubmit }) {
  const [errorMessage, setErrorMessage] = useState("");

  const { register, handleSubmit } = useForm({
    defaultValues: {
      nombre: "",
      email: "",
      mensaje: ""
    }
  });

  const handleFormSubmit = async (data) => {
    try {
      // onSubmit puede fallar, simulando un backend real
      await onSubmit(data);

      // NO mostramos nada aquí porque este componente es solo para error
    } catch (error) {
      // Mostramos mensaje genérico de error
      setErrorMessage("Ocurrió un error");
    }
  };

  return (
    <form onSubmit={handleSubmit(handleFormSubmit)}>
      <input type="text" placeholder="Nombre" {...register("nombre")} />
      <input type="email" placeholder="Email" {...register("email")} />
      <textarea placeholder="Mensaje" {...register("mensaje")} />

      <button type="submit">Enviar</button>

      {/* Mostramos el mensaje si el backend falló */}
      {errorMessage && <p>{errorMessage}</p>}
    </form>
  );
}

import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import { SimpleBackendFormError } from "./SimpleBackendFormError";

describe("SimpleBackendFormError", () => {
  it("muestra 'Ocurrió un error' cuando el backend falla", async () => {
    // Simulamos un error del backend
    const mockOnSubmit = jest.fn().mockRejectedValue(new Error("fail"));

    render(<SimpleBackendFormError onSubmit={mockOnSubmit} />);

    // Llenamos los campos
    fireEvent.change(screen.getByPlaceholderText("Nombre"), {
      target: { value: "Gabriel" }
    });
    fireEvent.change(screen.getByPlaceholderText("Email"), {
      target: { value: "[email protected]" }
    });
    fireEvent.change(screen.getByPlaceholderText("Mensaje"), {
      target: { value: "Hola" }
    });

    // Enviamos el formulario
    fireEvent.click(screen.getByText("Enviar"));

    // Esperamos a que aparezca el mensaje de error
    await waitFor(() => {
      expect(screen.getByText("Ocurrió un error")).toBeInTheDocument();
    });
  });
});

Conclusión

Es importante asegurar que los formularios funcionen como se espera, de esta manera el usuario podrá utilizar nuestros servicios correctamente. React Hook Form permite manejar los formularios de una forma más simple y sencilla. Mientras React Testing Library facilita interactuar y probar con los formularios.

Si quieres aprender a probar tus tablas como un profesional, tengo un libro enfocado únicamente en testing en React o un HUB donde hablo sobre testing en React.


HUB: Testing en React
Libro: Testing en React: Guía práctica con Jest y React Testing Library