Hacer pruebas unitarias con Jest en proyectos frontend

  • Jest ofrece un entorno de pruebas unitarias potente y casi sin configuración para proyectos frontend con JavaScript y React.
  • Las pruebas unitarias mejoran la detección temprana de errores, documentan el código y hacen más seguro el refactoring.
  • React Testing Library y las capacidades de mocks, tests asíncronos y cobertura de Jest facilitan probar componentes y lógica de interfaz.
  • Integrar npm test y los informes de cobertura en el flujo de trabajo diario aumenta de forma notable la calidad y estabilidad del frontend.

jest

Si desarrollas interfaces en React u otros frameworks de frontend, tarde o temprano te das cuenta de que fiarlo todo a “funciona en mi máquina” es jugar con fuego. Un pequeño cambio en un componente, una refactorización rápida o una dependencia que se actualiza pueden romper partes de la aplicación sin que te des ni cuenta… Salvo que tengas un buen sistema de pruebas unitarias montado con Jest.

El ecosistema moderno de JavaScript lleva las pruebas automatizadas en el ADN. Herramientas como Jest hacen que escribir tests para componentes, funciones y hooks sea algo asumible en el día a día, incluso si no eres un fanático del testing. La clave está en tener una configuración cómoda, entender bien cómo se escriben los tests y saber interpretar los resultados y la cobertura para detectar qué zonas del código se están quedando sin comprobar.

Por qué hacer pruebas unitarias en proyectos frontend

Las pruebas unitarias son pequeños tests que validan piezas concretas de tu código (funciones, componentes, hooks, utilidades…). En frontend son especialmente útiles porque la interfaz cambia mucho, hay lógica de estado, eventos del usuario, llamadas asíncronas, etc. Sin una red de seguridad, cada cambio es una lotería.

Entre los beneficios más claros de las pruebas unitarias destacan la detección temprana de errores. En lugar de descubrir fallos cuando el usuario ya está en producción, los detectas nada más guardar cambios y lanzar la batería de tests, lo que ayuda a comprender mejor el ciclo de vida de un bug. Esto ahorra tiempo, dinero y muchos dolores de cabeza al equipo.

Otro punto muy potente es que los tests acaban funcionando como documentación viva. Ver cómo está escrito un test sobre un componente o una función deja claro cómo se espera que se use, qué entradas admite y qué resultados debería devolver. En proyectos grandes, esto es oro para quienes se incorporan nuevos al equipo.

En el contexto de JavaScript y React, escribir tests también ayuda a modularizar mejor el código. Para poder probar una pieza por separado, necesitas que esté bien aislada, con dependencias claras y responsabilidades acotadas, lo que se traduce en un frontend más mantenible a medio y largo plazo.

jest

Qué es Jest y por qué se usa tanto en frontend

Jest es un framework de pruebas para JavaScript desarrollado originalmente por Facebook, pensado para funcionar de maravilla con React, pero totalmente válido para cualquier proyecto de JavaScript o TypeScript del lado del cliente o del servidor.

Una de sus grandes bazas es la filosofía de “cero configuración”. En muchos proyectos basta instalarlo y añadir un script en el package.json para empezar a correr tests sin volverte loco con ficheros de configuración complejos. Esto lo hace especialmente atractivo en entornos frontend donde ya hay muchas herramientas en juego.

Jest integra de serie características clave para proyectos frontend: ejecución rápida de pruebas, modo watch que relanza los tests al detectar cambios, soporte muy cómodo para código asíncrono, mocks y spies para simular dependencias, y generación de informes de cobertura de código sin depender de herramientas externas adicionales.

Para proyectos frontend con React, Jest se combina casi siempre con React Testing Library, una biblioteca que facilita probar componentes a través de su comportamiento y lo que renderizan, en lugar de atarse en exceso a la implementación interna. Entre ambas herramientas cubres prácticamente todas las necesidades habituales de testing en la interfaz.

Jest y React Testing Library para componentes

Instalación de Jest y React Testing Library en un proyecto frontend

El primer paso para empezar con Jest es añadirlo como dependencia de desarrollo en tu proyecto. Si utilizas npm, el comando típico sería:

npm install –save-dev jest

Si prefieres trabajar con yarn, puedes instalarlo con:

yarn add –dev jest

En proyectos basados en React es muy habitual instalar también React Testing Library, que se compone de varios paquetes: la base @testing-library/react para componentes, @testing-library/jest-dom para matchers adicionales sobre el DOM, y a menudo @testing-library/user-event para simular interacciones complejas de usuario.

Una instalación típica para React podría ser algo como:

npm install –save-dev @testing-library/react @testing-library/jest-dom @testing-library/user-event

Con estas dependencias en su sitio, tu entorno queda preparado para escribir tests unitarios centrados en componentes, eventos y resultados visibles para el usuario, apoyándote siempre en Jest como motor principal de ejecución de pruebas.

Configuración básica de Jest en package.json

Una vez instalado Jest, necesitas decirle al proyecto cómo se van a lanzar las pruebas. Lo habitual es añadir un script en el archivo package.json para tener un comando sencillo desde la terminal.

Un ejemplo mínimo de configuración podría ser:

{ «scripts»: { «test»: «jest» } }

Con este script puedes ejecutar todos los tests del proyecto lanzando simplemente:

npm test

o, si usas yarn, con:

yarn test

Jest detecta automáticamente los archivos de test en función de su nombre. Por defecto, buscará ficheros con sufijos .test.js o .spec.js en tu árbol de carpetas, de forma que no suele hacer falta indicarle rutas manualmente mientras respetes estas convenciones.

Archivos de prueba y ejemplos de Jest

Convención de archivos: extensión .test.js y estructura de tests

Para que Jest reconozca tus tests sin configuración extra, es muy recomendable usar la extensión .test.js (o .test.ts si trabajas con TypeScript). Por ejemplo, si tienes un componente Button.jsx, un nombre muy común para su test sería Button.test.js en el mismo directorio o en una carpeta de tests paralela.

Esta convención tiene dos ventajas claras:

  • Por un lado, Jest localiza automáticamente los archivos a ejecutar.
  • Por otro, cualquier persona que llegue nueva al proyecto sabe enseguida qué ficheros contienen código de producción y cuáles contienen pruebas.

Las pruebas se definen mediante la función test o su alias it, donde el primer argumento es una descripción en texto de lo que estás comprobando y el segundo es una función que ejecuta la lógica de la prueba. Dentro de esa función se usan las aserciones, a través de expect y sus distintos matchers.

En componentes de React la estructura es similar, solo que en lugar de probar una función pura, renderizas el componente con React Testing Library, buscas elementos en pantalla (por texto, rol, etiquetas, etc.) y compruebas que se muestren o reaccionen como esperas cuando simulas interacciones del usuario.

Escribir tu primera prueba unitaria con Jest

Para bajar todo esto a tierra, imagina una función sencilla que suma dos valores. En un archivo sum.js defines function sum(a, b) { return a + b; } y la exportas. A continuación, en sum.test.js importas esa función y defines un test con una descripción clara de lo que debería suceder.

El cuerpo de la prueba se limita a ejecutar la función y validar el resultado: llamas a sum(1, 2) y usas expect para indicar que el valor debe ser exactamente 3. Si la función deja de devolver ese resultado (por un bug o una modificación no prevista), Jest marcará el test como fallido.

Este tipo de prueba, por simple que parezca, es la base del testing unitario. Cada función o unidad lógica tiene uno o varios tests que describen lo que debería hacer en diferentes escenarios, de manera que cualquier desviación del comportamiento esperado salta a la vista con solo ejecutar la batería de pruebas.

En componentes de frontend el planteamiento es igual de directo: renderizas el componente, compruebas qué se muestra, simulas eventos como clics o escritura en inputs y validas que el estado y el DOM resultante coinciden con lo que define el diseño funcional de la aplicación.

Conforme el proyecto crece, irás añadiendo más pruebas para cubrir casos límite, entradas atípicas, errores y situaciones menos obvias, reforzando la fiabilidad de la aplicación y evitando regresiones cuando introduzcas nuevas funcionalidades.

Matchers de Jest: distintas formas de comprobar resultados

El corazón de las aserciones en Jest es la función expect, que se encadena con diferentes matchers para expresar qué debe cumplir el valor recibido. Dependiendo de qué estés probando, te interesará usar uno u otro. Estos son los matchers más prácticos:

  • toBe. Comprueba igualdad estricta, lo que en JavaScript implica el mismo valor y el mismo tipo, muy útil para números, cadenas o booleanos donde esperas una coincidencia exacta. Si esperas 3, no te vale que llegue «3».
  • toEqual. Útil cuando trabajas con objetos o arrays. Este matcher compara la estructura y el contenido de los objetos, de manera que puedes verificar que una función devuelve un objeto con las propiedades y valores correctos, aunque la referencia interna no sea la misma.
  • not. Si en algún momento necesitas asegurar que algo NO ocurre, dispones de la negación not. Por ejemplo, expect(valor).not.toBe(0) deja claro que un número no debería ser cero, o expect(array).not.toEqual([]) indica que no esperas un array vacío.

Además de estos básicos, Jest ofrece muchos otros matchers: para comprobar que una función lanza un error, que un array contiene un elemento concreto, que una cadena hace match con una expresión regular, o, con jest-dom en el caso de React, que un elemento del DOM está visible, deshabilitado, tiene cierto texto, etc.

pruebas asimétricas jest

Pruebas asíncronas en Jest: promesas, async/await y callbacks

El frontend moderno está lleno de operaciones asíncronas: peticiones HTTP, timers, interacciones del usuario que desencadenan actualizaciones de estado, etc. Por eso, Jest integra varias formas de trabajar cómodamente con pruebas asíncronas.

La manera más limpia y habitual de probar lógica asíncrona es usar async/await. Declaras tu función de test como asíncrona, esperas a que se resuelva la promesa que estás comprobando y luego haces las aserciones habituales con expect sobre el resultado recibido.

Por ejemplo, podrías tener una función fetchData que devuelve una promesa y escribir un test asíncrono que llame a fetchData, espere a que se resuelva y verifique que el dato devuelto coincide con lo que esperas, ya sea un texto concreto o un objeto con una estructura determinada.

Jest también soporta directamente las promesas sin async/await, devolviendo la propia promesa desde el test para que el framework sepa cuándo ha terminado la operación. Además, en casos viejos o muy específicos, permite usar callbacks con un parámetro done para indicar el final de la prueba.

En el terreno de React Testing Library, las pruebas asíncronas suelen combinar esperas con findBy o waitFor, que permiten aguardar a que el DOM se actualice tras una petición o un cambio de estado antes de hacer las aserciones pertinentes.

Mocking en Jest: simulando módulos, funciones y dependencias

Un principio básico de las pruebas unitarias es aislar la unidad bajo prueba. Eso implica que, si una función o componente depende de servicios externos (APIs, librerías de terceros, módulos pesados…), te interesa simular esos comportamientos en lugar de ejecutarlos realmente durante el test.

Jest facilita este aislamiento a través de los mocks. Con jest.fn puedes crear funciones simuladas que registran cuántas veces se llaman, con qué argumentos, o qué valor deberían devolver. Esto resulta muy útil para comprobar interacciones internas sin tener que tocar el código real de esos servicios.

Cuando necesitas ir un paso más allá, jest.mock te permite reemplazar módulos enteros. Puedes indicar que, al importar un determinado archivo, Jest use una implementación falsa que devuelva valores controlados, evitando, por ejemplo, lanzar peticiones HTTP reales cada vez que se ejecuta la batería de pruebas.

En componentes de React, los mocks se utilizan con frecuencia para simular hooks personalizados, servicios de datos o módulos que gestionan almacenamiento local, analítica, etc., manteniendo la prueba centrada en el comportamiento del componente y no en el de sus dependencias externas.

Bien utilizado, el mocking acelera muchísimo la ejecución de los tests y permite reproducir fácilmente escenarios de error, respuestas extrañas del servidor o estados atípicos que serían complicados de conseguir interactuando con los servicios reales.

Organizar y agrupar pruebas con bloques describe

A medida que tu suite de pruebas crece, necesitas mantener un mínimo de orden para no perderte entre cientos de tests repartidos por varios archivos. Jest ofrece los bloques describe como forma natural de agrupar pruebas relacionadas.

Con describe puedes encerrar varios tests bajo un mismo contexto, por ejemplo “operaciones aritméticas” o “comportamiento del componente Header”. Dentro del bloque, cada test describe un caso concreto, pero el conjunto se lee como una especie de historia coherente sobre esa parte del código.

Esta organización ayuda tanto a nivel de lectura como a la hora de depurar. Cuando algo falla, es más sencillo localizar el grupo de pruebas y entender a qué parte del sistema afecta, sin necesidad de escanear todo el proyecto.

Además, describe se combina muy bien con hooks de ciclo de vida de Jest, como beforeEach o afterEach, permitiendo preparar datos o limpiar estados compartidos para todas las pruebas dentro del mismo bloque, evitando duplicar lógica de inicialización en cada test individual.

En proyectos frontend complejos es habitual tener un describe por componente, subdividido si hace falta en descripciones internas para diferentes modos, props o flujos de interacción, lo que convierte el archivo de test en un mapa bastante claro de todo lo que se espera de ese componente.

Uso de npm test y disciplina de ejecución en el flujo de trabajo

Con la configuración básica lista, el comando npm test se convierte en tu aliado diario. Lanzarlo antes de subir cambios debería ser un gesto casi automático, como guardar el archivo o ejecutar el linter.

Muchos equipos adoptan la norma no escrita de que no se hace commit si los tests no pasan. Esta costumbre evita que la rama principal del proyecto se rompa y mantiene una calidad mínima garantizada en cada merge o pull request que se integra al repositorio.

Jest también ofrece un modo interactivo muy útil en desarrollo. Al ejecutar npm test en modo watch, el framework vuelve a correr solo los tests relacionados con los archivos que modificas, lo que acelera mucho el ciclo de probar y corregir mientras desarrollas nuevas features o arreglas bugs.

Integrar Jest en un sistema de integración continua (CI) como GitHub Actions completa el círculo. Cada vez que alguien sube código al repositorio remoto, el servidor de CI ejecuta npm test y bloquea la integración si la suite falla, evitando que los errores se cuelen en entornos compartidos.

Cobertura de código: midiendo cuánto se prueba realmente

No basta con tener algunos tests escritos. También interesa saber hasta qué punto están cubriendo el código. Para eso, Jest integra generación de informes de cobertura que indican qué líneas, funciones y ramas se han ejecutado durante las pruebas.

Generar estos informes es tan sencillo como añadir el flag –coverage al comando de test. Por ejemplo, puedes configurar el script de package.json como «test»: «jest –coverage» o ejecutar npm test — –coverage cuando quieras un informe detallado.

El resultado incluye porcentajes de cobertura por archivo y a nivel global. Verás qué ficheros tienen buena cobertura y cuáles apenas se tocan durante las pruebas, lo que sirve de guía para decidir dónde merece la pena invertir esfuerzo adicional escribiendo más tests.

Eso sí, conviene recordar que un 100 % de cobertura no garantiza ausencia de errores. Es posible ejecutar todas las líneas del código con tests poco exigentes, así que la calidad de las aserciones es igual o más importante que el número en sí.

Usar la cobertura como indicador aproximado, combinado con revisiones de código y sentido común, es una buena forma de mantener el equilibrio entre esfuerzo en testing y beneficios reales para la estabilidad del proyecto.

Con todo lo visto, usar Jest y herramientas como React Testing Library en proyectos frontend se convierte en una decisión bastante lógica: te permite comprobar que cada componente y función hace lo que debe, ejecutar las pruebas fácilmente con npm test, vigilar la cobertura con –coverage y mantener una base de código sólida en la que es mucho más seguro evolucionar el producto sin temor a romper lo que ya funcionaba.

Crear un pipeline CI/CD con GitHub Actions
Artículo relacionado:
Cómo crear un pipeline CI/CD sólido con GitHub Actions