Cómo hacer testing de formularios en React con Formik y React Testing Library

Gabriel Jiménez | Hace 7 días

Crear formularios en React parece sencillo hasta que empiezan a crecer: validaciones, lógica de negocio, campos condicionales y flujos cada vez más frágiles. Sin pruebas, cualquier cambio se vuelve un riesgo y un simple refactor puede romper algo en producción. Formik ayuda a definir un flujo para trabajar tus formularios, pero el verdadero valor aparece cuando lo acompañas de testing. En este artículo aprenderás cómo probar formularios en React con Formik, validando desde los inputs hasta el submit completo, para ganar confianza y mantener tus formularios escalables.

¿Qué es Formik y por que usarlo para crear mis formularios React?

Formik es una librería para manejar el flujo de un formulario. Un formulario no solo es colocar inputs de texto, un poco de css y conectar con el backend, si no, en muchos casos es más complejo que eso. Generalmente, en un flujo de un formulario tenemos:


  1. Sincronización de los campos
  1. Validaciones
  1. Preparar la data para enviarla al backend
  1. Ocultar y mostrar campos
  1. Lógica de negocio
  1. Notificaciones al usuario
  1. Evitar re renders

Las librerías de formularios como Formik se encargan de esto y más. ¿Pero por qué muchos de los desarrollos en React no usan estas librerías? Puede deberse a:


  • Desconocimiento que existen librerías para formularios
  • Curva de aprendizaje
  • Fechas de entrega ajustadas
  • Agregar más peso a la aplicación
  • Mantener actualizada la librería
  • Perder soporte si el dueño decide deprecar

Cual sea la situación, es importante conocer el problema que resuelven estas librerías como Formik, React Hook Form, React Final Form.

¿Por qué debería hacer testing de formularios en React?

Deberías de probar tus formularios por tres razones:


  1. Permiten escalar tus aplicaciones
  1. Detectar bugs
  1. Documentación interna para otros desarrolladores

Permiten escalar tus aplicaciones

La escalabilidad es una atributo muy importante en el desarrollo de software, permite que una aplicación crezca y se mantenga estable ante cualquier cambio. Para lograr esto, las pruebas son un gran aliado. Las pruebas nos permiten hacer cambios con más confianza, ya que aseguran que nuestros cambios no impacten en otros lugares de la aplicación.


Detectar bugs

Teniendo pruebas podemos minimizar el impacto de los bugs que llegan a producción. Sin embargo, a veces es muy difícil de lograrlo, porque podemos probar todo el sistema en un 100 %, pero siempre existen factores externos a nosotros que no controlamos, por ejemplo: problemas de red, capacidad de los dispositivos del usuario, apagones de luz, catástrofes naturales. Aun así, es preferible tener 10 bugs que 80 bugs llegando a producción.


Documentación interna para otros desarrolladores

Como desarrolladores es importante tener documentación sobre como funciona internamente nuestras aplicaciones, y más aun si el equipo es grande. Pero no solo eso, te reto a que no toques una parte de código por meses y luego vuelvas a tocar, dime si te da confianza de modificar el código.


La documentación en forma de pruebas es una de las mejores maneras que un desarrollador tiene para validar que su aplicación hace lo que dice hacer.

Herramientas para hacer testing de formularios

Para probar nuestros formularios necesitamos de dos herramientas indispensables:


  1. Jest. Es un framework Javascript enfocado en testing. Permite escribir, ejecutar y evaluar las pruebas fácilmente.
  1. React Testing Library. Es una librería para poder probar nuestros componentes React de una forma más sencilla.

Cómo probar inputs controlados por Formik

Es importante conocer como probar cada uno de los tipos de campos que existen: texto, checkbox, radio button, select. Algunos parecen que se prueban igual, pero hay pequeños detalles que los hacen diferentes.


Probar campos de tipo texto usando Formik


File: src/formik/LoginForm.jsx
    1: import { useFormik } from "formik";
    2: 
    3: export function LoginForm() {
    4:   const formik = useFormik({
    5:     initialValues: {
    6:       email: "",
    7:       password: "",
    8:     },
    9:     onSubmit: () => {},
   10:   });
   11: 
   12:   return (
   13:     <form onSubmit={formik.handleSubmit}>
   14:       <label htmlFor="email">Email</label>
   15:       <input
   16:         id="email"
   17:         name="email"
   18:         type="email"
   19:         value={formik.values.email}
   20:         onChange={formik.handleChange}
   21:       />
   22: 
   23:       <label htmlFor="password">Password</label>
   24:       <input
   25:         id="password"
   26:         name="password"
   27:         type="password"
   28:         value={formik.values.password}
   29:         onChange={formik.handleChange}
   30:       />
   31: 
   32:       <button type="submit">Enviar</button>
   33:     </form>
   34:   );
   35: }

File: src/formik/LoginForm.test.jsx
    1: import { render, screen, fireEvent } from "@testing-library/react";
    2: import {LoginForm} from "./LoginForm.jsx";
    3: 
    4: describe("LoginForm", () => {
    5:   it("permite escribir en los inputs controlados por Formik", () => {
    6:     render(<LoginForm />);
    7: 
    8:     const emailInput = screen.getByLabelText(/email/i);
    9:     const passwordInput = screen.getByLabelText(/password/i);
   10: 
   11:     fireEvent.change(emailInput, {
   12:       target: { value: "[email protected]" },
   13:     });
   14: 
   15:     fireEvent.change(passwordInput, {
   16:       target: { value: "123456" },
   17:     });
   18: 
   19:     expect(emailInput).toHaveValue("[email protected]");
   20:     expect(passwordInput).toHaveValue("123456");
   21:   });
   22: });


Analicemos


Línea 4

Agrupamos las pruebas para el componente “LoginForm” usando la palabra reservada “describe”.


Línea 5

Definimos nuestra prueba usando la palabra reservada “it”


Línea 6

Usamos React Testing Library para montar nuestro componente en un navegador simulado usando la palabra reservada “render”.


Línea 8..17

Escribimos en los campos nombre y password.


Línea 19..20

Validamos que la data ingresada se este sincronizando correctamente.


Probar campos de tipo seleccionable


File: src/formik/LoginForm.jsx
. . .
    3: export function LoginForm() {
    4:   const formik = useFormik({
    5:     initialValues: {
. . .
    8:       role: "",
    9:     },
   10:     onSubmit: () => {},
   11:   });
   12: 
   13:   return (
   14:     <form onSubmit={formik.handleSubmit}>
. . .
   33:       <label htmlFor="role">Rol</label>
   34:       <select
   35:         id="role"
   36:         name="role"
   37:         value={formik.values.role}
   38:         onChange={formik.handleChange}
   39:       >
   40:         <option value="">Selecciona un rol</option>
   41:         <option value="admin">Administrador</option>
   42:         <option value="cashier">Cajero</option>
   43:       </select>
   44: 
   45:       <button type="submit">Enviar</button>
   46:     </form>
   47:   );
   48: }

File: src/formik/LoginForm.test.jsx
. . .
   18:   it("permite seleccionar una opción en un campo select controlado por Formik", () => {
   19:     render(<LoginForm />);
   20: 
   21:     const roleSelect = screen.getByLabelText(/rol/i);
   22: 
   23:     fireEvent.change(roleSelect, { target: { value: "cashier" } });
   24: 
   25:     expect(roleSelect).toHaveValue("cashier");
   26:   });
   27: });


Analicemos


Línea 21..23

Seleccionamos la opción ”cashier” de campo rol. Es importante que la opción exista dentro del campo “select”.


Línea 25

Validamos que la opción se ha seleccionado correctamente.

Probar campos de tipo checkbox


File: src/formik/LoginForm.jsx
. . .
    3: export function LoginForm() {
    4:   const formik = useFormik({
    5:     initialValues: {
. . .
    9:       remember: false,
   10:     },
   11:     onSubmit: () => {},
   12:   });
   13: 
   14:   return (
   15:     <form onSubmit={formik.handleSubmit}>
. . .
   46:       <label htmlFor="remember">
   47:         <input
   48:           id="remember"
   49:           name="remember"
   50:           type="checkbox"
   51:           checked={formik.values.remember}
   52:           onChange={formik.handleChange}
   53:         />
   54:         Recordarme
   55:       </label>
. . .
   59:   );
   60: }

File: src/formik/LoginForm.test.jsx
. . .
    4: describe("LoginForm", () => {
. . .
   28:   it("permite marcar y desmarcar un checkbox controlado por Formik", () => {
   29:     render(<LoginForm />);
   30: 
   31:     const rememberCheckbox = screen.getByLabelText(/recordarme/i);
   32: 
   33:     expect(rememberCheckbox).not.toBeChecked();
   34: 
   35:     fireEvent.click(rememberCheckbox);
   36: 
   37:     expect(rememberCheckbox).toBeChecked();
   38:   });
   39: });


Analicemos


Línea 31

Buscamos el campo “Recordarme”.


Línea 33

Validamos que no este seleccionado.


Línea 35

Hacemos clic en el campo para que se recuerde nuestra contraseña.


Línea 37

Valimos que el campo este seleccionado.

Probar campos de tipo radio button


File: src/formik/LoginForm.jsx
. . .
    3: export function LoginForm() {
    4:   const formik = useFormik({
    5:     initialValues: {
. . .
   10:       userType: "",
   11:     },
   12:     onSubmit: () => {},
   13:   });
   14: 
   15:   return (
   16:     <form onSubmit={formik.handleSubmit}>
. . .
   59:       <fieldset>
   60:         <legend>Tipo de usuario</legend>
   61: 
   62:         <label htmlFor="employee">
   63:           <input
   64:             id="employee"
   65:             type="radio"
   66:             name="userType"
   67:             value="employee"
   68:             checked={formik.values.userType === "employee"}
   69:             onChange={formik.handleChange}
   70:           />
   71:           Empleado
   72:         </label>
   73: 
   74:         <label htmlFor="manager">
   75:           <input
   76:             id="manager"
   77:             type="radio"
   78:             name="userType"
   79:             value="manager"
   80:             checked={formik.values.userType === "manager"}
   81:             onChange={formik.handleChange}
   82:           />
   83:           Gerente
   84:         </label>
   85:       </fieldset>
. . .
   89:   );
   90: }

File: src/formik/LoginForm.test.jsx
. . .
    4: describe("LoginForm", () => {
. . .
   40:   it("permite seleccionar un radio controlado por Formik", () => {
   41:     render(<LoginForm />);
   42: 
   43:     const employeeRadio = screen.getByLabelText(/empleado/i);
   44:     const managerRadio = screen.getByLabelText(/gerente/i);
   45: 
   46:     expect(employeeRadio).not.toBeChecked();
   47:     expect(managerRadio).not.toBeChecked();
   48: 
   49:     fireEvent.click(employeeRadio);
   50:     expect(employeeRadio).toBeChecked();
   51:     expect(managerRadio).not.toBeChecked();
   52: 
   53:     fireEvent.click(managerRadio);
   54:     expect(managerRadio).toBeChecked();
   55:     expect(employeeRadio).not.toBeChecked();
   56:   });
   57: });


Analicemos


Línea 43

Buscamos los campos radio button “Empleado” y “Gerente”


Línea 46..47

Valimos que ambos campos no esten seleccionados.


Línea 49..51

Seleccionamos que somos un “Empleado”, y validamos que el campo “Empleado” se encuentre seleccionado, mientras el "Gerente” siga sin selección.


Línea 53..55

Hacemos lo contrario , seleccionamos que somos “Gerente”, y validamos que no el campo “Gerente” este seleccionado, mientras “Empleado” se deshabilite.


Cómo probar el submit de un formulario en React con Formik

Ya vimos como probar campo por campo. Sin embargo, como desarrolladores frontend nos interesa asegurar que la comunicación entre el frontend y el backend se lleve acabo correctamente. Por lo tanto, la prueba debe asegurar que la data esta siendo enviada al backend con la estructura y el formato correcto.

File: src/formik/LoginForm.jsx
. . .
    3: export function LoginForm({ onSubmit }) {
    4:   const formik = useFormik({
. . .
   12:     onSubmit: values => onSubmit(values),
   13:   });
   14: 
   15:   return (
   16:     <form onSubmit={formik.handleSubmit}>
. . .
   86:       <button type="submit">Enviar</button>
   87:     </form>
   88:   );
   89: }

File: src/formik/LoginForm.test.jsx
. . .
    4: describe("LoginForm", () => {
. . .
   58:   it("envía los valores correctos al hacer submit", async () => {
   59:     const handleSubmit = jest.fn();
   60: 
   61:     render(<LoginForm onSubmit={handleSubmit} />);
   62: 
   63:     fireEvent.change(screen.getByLabelText(/email/i), {
   64:       target: { value: "[email protected]" },
   65:     });
   66: 
   67:     fireEvent.change(screen.getByLabelText(/password/i), {
   68:       target: { value: "123456" },
   69:     });
   70: 
   71:     fireEvent.change(screen.getByLabelText(/rol/i), {
   72:       target: { value: "admin" },
   73:     });
   74: 
   75:     fireEvent.click(screen.getByLabelText(/recordarme/i));
   76:     fireEvent.click(screen.getByLabelText(/empleado/i));
   77: 
   78:     fireEvent.click(screen.getByRole("button", { name: /enviar/i }));
   79: 
   80:     await waitFor(() => {
   81:       expect(handleSubmit).toHaveBeenCalledTimes(1);
   82:       expect(handleSubmit).toHaveBeenCalledWith(
   83:         {
   84:           email: "[email protected]",
   85:           password: "123456",
   86:           role: "admin",
   87:           remember: true,
   88:           userType: "employee",
   89:         },
   90:       );
   91:     });
   92:   });
   93: });


Analicemos


Línea 59

Creamos un mock para podemos saber si la función “onSubmit” del formulario se esta invocando con los parámetros correctos.


Línea 61..78

Montamos el componente y seleccionamos cada uno de los campos.


Línea 80..91

En lugar de probar campo por campo, probamos que la data enviada al backend tenga el formato y estructura que se espera.


Conclusión

Utilizar Formik para crear tus formularios te permiten mantener una estructura en como se gestionan, pero las pruebas, te permiten que tus aplicaciones escalen y te den confianza para modificar sin temor a romper algo que antes ya funcionaba.


¿Quieres aprender más casos y cómo testear formularios con otras librerías? Visita mi Hub de Testing en React.