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.
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:
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:
Cual sea la situación, es importante conocer el problema que resuelven estas librerías como Formik, React Hook Form, React Final Form.
Deberías de probar tus formularios por tres razones:
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.
Para probar nuestros formularios necesitamos de dos herramientas indispensables:
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.
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.
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.
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.
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.
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.
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.