Cómo probar los props de un componente en React con Jest y React Testing Library

Gabriel Jiménez | Hace 1 día

Los props son una parte esencial de los componentes en React, validar su comportamiento debería ser obligatorio en nuestros desarrollos. Sin embargo, en muchos de los casos desconocemos cómo hacerlo. 


En este artículo, aprenderemos los diferentes tipos de props que tenemos y como deberíamos probar cada uno.


Qué son los props en React

De forma muy general, las props (properties) son valores que pasamos a nuestros componentes para realizar una tarea específica como modificar el comportamiento o apariencia.


NOTA: Si quieres saber sobre buenas prácticas en los props, tengo un artículo que puede ayudarte: React props: buenas prácticas para código más limpio y escalable


Tipos de props qué existen en React

Entre los props más comunes que podemos tener son: props de acción externa, de control interno y de visibilidad. 


Veamos de que trata cada uno:


  • Prop de acción externa. Se encargan de comunicar con algún servicio externo, ya sea un API o cualquier otro evento.
  • Prop de control interno. Se usan para inicializar algún estado interno del componente.
  • Prop de visibilidad. Permite ocultar o mostrar algún comportamiento o apariencia dentro del componente.

Por qué deberías probar los props de tus componentes

Como sabemos los props permiten modificar el comportamiento de un componente. Este comportamiento, puede incluir lógica de negocio como ocultar o mostrar un campo. Tal vez esto a simple vista no parezca critico, pero puede ocasionar la perdida de un posible cliente.


Alguno de los puntos a considerar de porqué deberíamos probar nuestros props son:


  • Garantizar que el componente se comporte como se espera.
  • Evitar errores al modificar componentes reutilizables.
  • Documentar los comportamientos del componente con cada uno de los props y sus valores.
  • Prevenir errores de integración otros componentes.
  • Permitir refactorizar con más confianza.
  • Refuerza la mentalidad de pruebas.

Ejemplo práctico: cómo probar las props paso a paso con Jest y Testing Library


Prop de acción externa

El objetivo de probar este prop, es validar que se ha invocado con los parámetros correctos.


Veamos el siguiente ejemplo:

    1: import React, { useState } from "react";
    2: 
    3: const PeliculasForm = ({ crearPeliculaApi }) => {
    4:   const [values, setValues] = useState({
    5:     nombre: "",
    6:   });
    7: 
    8:   function handleChange({ target }) {
    9:     setValues({ ...values, [target.name]: target.value });
   10:   }
   11: 
   12:   async function onSubmit(e) {
   13:     e.preventDefault();
   14:     await crearPeliculaApi(values);
   15:   }
   16: 
   17:   return (
   18:     <form onSubmit={onSubmit}>
   19:       <input
   20:         type="text"
   21:         name="nombre"
   22:         placeholder="Nombre de la película"
   23:         data-testid="nombre"
   24:         value={values.nombre}
   25:         onChange={handleChange}
   26:       />
   27: 
   28:       <button type="submit">
   29:         Guardar
   30:       </button>
   31:     </form>
   32:   );
   33: };
   34: 
   35: export default PeliculasForm;


El componente es bastante sencillo, recibe un prop “crearPeliculaApi” que a la hora de invocarse le enviamos la data de la película.


Nuestra prueba, debe validar que esa data se esta pasando a la hora de invocar al prop.


Veamos como luce la prueba:

    1: import {fireEvent, render, screen, waitFor} from "@testing-library/react";
    2: import PeliculasForm from "./PeliculasForm";
    3: 
    4: describe('PeliculasForm', () => {
    5:   it('crear una pelicula correctamente', async () => {
    6:     const props = { crearPeliculaApi: jest.fn() }
    7:     render(<PeliculasForm { ...props } />);
    8: 
    9:     fireEvent.change(screen.getByTestId("nombre"), { target: { value: "El silencio de los inocentes" } });
   10:     fireEvent.submit(screen.getByText("Guardar"))
   11: 
   12:     await waitFor(() => {
   13:       expect(props.crearPeliculaApi).toHaveBeenCalledWith({ nombre: "El silencio de los inocentes"});
   14:     })
   15:   });
   16: });


Analicemos


Línea 4

Agrupamos las pruebas para el componente “PeliculasForm”, utilizando la palabra reserva “describe”.


Líne 5

Creamos la prueba “crear un película correctamente”, utilizando la palabra reserva “it”.


Línea 6

Creamos el prop “crearPeliculaApi” y le decimos que se va a comportar como un tipo de doble Spy.


Línea 7

Renderizamos el componente usando la función “render” de React Testing Library.


Línea 9

Escribimos el nombre de la película, usando las funciones screen y fireEvent.


Línea 10

Hacemos clic en el botón Guardar.


Línea 12-14

Valimos que se ha llamado invocado el prop “crearPeliculaApi” con el objeto correctamente.


Prop de control interno

Para este prop, el objetivo es comprobar que el prop que se asigna a un estado interno cambie el comportamiento del componente.


Analicemos el siguiente código:

    1: import React, { useState } from "react";
    2: 
    3: const PeliculasForm = ({ crearPeliculaApi, esPeliculaParaAdultos }) => {
    4:   const [values, setValues] = useState({
    5:     nombre: "",
    6:     tipo: esPeliculaParaAdultos ? "adultos" : "toda la familia",
    7:   });
    8: 
    9:   function handleChange({ target }) {
   10:     setValues({ ...values, [target.name]: target.value });
   11:   }
   12: 
   13:   async function onSubmit(e) {
   14:     e.preventDefault();
   15:     await crearPeliculaApi(values);
   16:   }
   17: 
   18:   return (
   19:     <form onSubmit={onSubmit}>
   20:       <input
   21:         type="text"
   22:         name="nombre"
   23:         placeholder="Nombre de la película"
   24:         data-testid="nombre"
   25:         value={values.nombre}
   26:         onChange={handleChange}
   27:       />
   28: 
   29:       <button type="submit">
   30:         Guardar
   31:       </button>
   32:     </form>
   33:   );
   34: };
   35: 
   36: export default PeliculasForm;


Recibimos el prop “esPeliculaParaAdultos”, que se usa para definir si una película debe ser marcada como apta para toda la familia.


La prueba debe validar que al enviar el valor “true” en el prop “esPeliculaParaAdultos”, el estado “tipo” se le asigne el valor “adultos”.


Veamos la prueba:

. . . 
    4: describe('PeliculasForm', () => {
. . .
   20:   it('crear una pelicula para adultos correctamente', async () => {
   21:     const props = {
   22:       crearPeliculaApi: jest.fn(),
   23:       esPeliculaParaAdultos: true
   24:     }
   25:     render(<PeliculasForm { ...props } />);
   26: 
   27:     fireEvent.change(screen.getByTestId("nombre"), { target: { value: "El silencio de los inocentes" } });
   28:     fireEvent.submit(screen.getByText("Guardar"))
   29: 
   30:     await waitFor(() => {
   31:       expect(props.crearPeliculaApi).toHaveBeenCalledWith({
   32:         nombre: "El silencio de los inocentes",
   33:         tipo: "adultos",
   34:       });
   35:     })
   36:   });
. . .


Analicemos


Línea 23

Definimos el prop “esPeliculaParaAdultos” como verdadero.


Línea 33

Comprobamos que al invocar la función “crearPeliculaApi” se envíe el campo “tipo” con el valor “adultos”.


Prop de visibilidad

Su objetivo es validar si uno o varios elementos se ocultan o se muestran.


Veamos el siguiente código:

    1: import React, { useState } from "react";
    2: 
    3: const PeliculasForm = ({ crearPeliculaApi, esPeliculaParaAdultos, esUsuarioAdmin }) => {
    4:   const [values, setValues] = useState({
    5:     nombre: "",
    6:     precio: "",
    7:     tipo: esPeliculaParaAdultos ? "adultos" : "toda la familia",
    8:   });
    9: 
   10:   function handleChange({ target }) {
   11:     setValues({ ...values, [target.name]: target.value });
   12:   }
   13: 
   14:   async function onSubmit(e) {
   15:     e.preventDefault();
   16:     await crearPeliculaApi(values);
   17:   }
   18: 
   19:   return (
   20:     <form onSubmit={onSubmit}>
   21:       <input
   22:         type="text"
   23:         name="nombre"
   24:         placeholder="Nombre de la película"
   25:         data-testid="nombre"
   26:         value={values.nombre}
   27:         onChange={handleChange}
   28:       />
   29: 
   30:       {
   31:         esUsuarioAdmin &&
   32:         <input
   33:           type="text"
   34:           name="precio"
   35:           placeholder="Precio"
   36:           data-testid="precio"
   37:           value={values.precio}
   38:           onChange={handleChange}
   39:         />
   40:       }
   41: 
   42:       <button type="submit">
   43:         Guardar
   44:       </button>
   45:     </form>
   46:   );
   47: };
   48: 
   49: export default PeliculasForm;


Recibimos el prop “esUsuarioAdmin” para mostrar el campo precio, en caso contrario lo ocultamos.




La prueba es la siguiente:

. . .
   38:   it('crear una pelicula para usuario admin correctamente', async () => {
   39:     const props = {
   40:       crearPeliculaApi: jest.fn(),
   41:       esPeliculaParaAdultos: true,
   42:       esUsuarioAdmin: true,
   43:     }
   44:     render(<PeliculasForm { ...props } />);
   45: 
   46:     fireEvent.change(screen.getByTestId("nombre"), { target: { value: "El silencio de los inocentes" } });
   47:     fireEvent.change(screen.getByTestId("precio"), { target: { value: "1000" } });
   48:     fireEvent.submit(screen.getByText("Guardar"))
   49: 
   50:     await waitFor(() => {
   51:       expect(props.crearPeliculaApi).toHaveBeenCalledWith({
   52:         nombre: "El silencio de los inocentes",
   53:         tipo: "adultos",
   54:         precio: "1000",
   55:       });
   56:     })
   57:   });
. . .


Analicemos


Línea 42

Definimos que el usuario es un admin para poder asignar un precio a la pelicula, para hacerlo asignamos true al prop “esUsuarioAdmin”.


Línea 47

Asignamos un precio a la película.


Línea 54

Al invocar el prop “crearPeliculaApi”, comprobamos que el campo “precio” venga con el valor asignado en la línea 47.


Errores comunes al probar props en React y cómo evitarlos

Estos son algunos de los errores más comunes que podemos encontrarnos:


  • Nombrar las pruebas con el nombre del prop que se está probando.
  • Enfocarnos en probar el comportamiento del prop en lugar del negocio.
  • Probar directamente los valores de los props al inicio de renderizar el componente.
  • Querer probar todos las combinaciones posibles de cada prop y por cada valor.
  • No eliminar props que ya no se usan.

Para evitarlos, debemos enfocarnos en la lógica de negocio, como lo hicimos en nuestro caso práctico.


Conclusiones

Los props son una parte muy importante en nuestros componentes, por lo que probar cada comportamiento que nuestros props generan es vital para tener componentes fáciles de mantener. Sin embargo, evitemos probar comportamientos que no sean tan críticos para el negocio.


Si quieres aprender más sobre pruebas Testing en React con Jest y Testing Library, te dejo estos recursos:


Programa mentalidad de pruebas


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


Todo sobre testing en React