El autor seleccionó Vets Who Code para recibir una donación como parte del programa Write for DOnations.
Introducción
Obtener una cobertura de pruebas sólida es imperativo para construir confianza en tu aplicación web. Jest es un corredor de pruebas de JavaScript que proporciona recursos para escribir y ejecutar pruebas. React Testing Library ofrece un conjunto de ayudas para pruebas que estructuran tus pruebas basadas en las interacciones del usuario en lugar de en los detalles de implementación de los componentes. Tanto Jest como React Testing Library vienen preempaquetados con Create React App y se adhieren al principio rector de que las pruebas de aplicaciones deben asemejarse a cómo se utilizará el software.
En este tutorial, probarás código asíncrono e interacciones en un proyecto de muestra que contiene varios elementos de interfaz de usuario. Usarás Jest para escribir y ejecutar pruebas unitarias, e implementarás React Testing Library como una biblioteca de DOM auxiliar (Document Object Model) para manejar la interacción con los componentes.
Prerrequisitos
Para completar este tutorial, necesitarás:
-
Node.js versión 14 o superior instalado en tu máquina local. Para instalar Node.js en macOS o Ubuntu 18.04, sigue los pasos en la sección Cómo Instalar Node.js y Crear un Entorno de Desarrollo Local en macOS o la sección Instalación Usando un PPA de Cómo Instalar Node.js en Ubuntu 18.04.
-
npm
versión 5.2 o superior en tu máquina local, que necesitarás para usar Create React App ynpx
en el proyecto de ejemplo. Si no instalastenpm
junto conNode.js
, hazlo ahora. Para Linux, usa el comandosudo apt install npm
.- Para que los paquetes de
npm
funcionen en este tutorial, instala el paquetebuild-essential
. Para Linux, usa el comandosudo apt install build-essential
.
- Para que los paquetes de
-
Git instalado en tu máquina local. Puedes verificar si Git está instalado en tu computadora o seguir el proceso de instalación para tu sistema operativo con Cómo Instalar Git en Ubuntu 20.04.
-
Familiaridad con React, que puedes desarrollar con la serie Cómo Codificar en React.js. Debido a que el proyecto de muestra está creado con Create React App, no necesitas instalarlo por separado.
-
Alguna familiaridad con Jest como un ejecutor de pruebas o framework es útil pero no es requerido. Debido a que Jest viene preempaquetado con Create React App, no necesitas instalarlo por separado.
Paso 1 — Configuración del Proyecto
En este paso, clonarás un proyecto de muestra y lanzarás la suite de pruebas. El proyecto de muestra utiliza tres herramientas principales: Create React App, Jest y React Testing Library. Create React App se utiliza para arrancar una aplicación de una sola página con React. Jest se utiliza como el corredor de pruebas, y React Testing Library proporciona ayudantes de prueba para estructurar pruebas en torno a interacciones de usuario.
Para empezar, clonarás una aplicación React preconstruida desde GitHub. Trabajarás con la aplicación Directorio de Perritos, que es un proyecto de muestra que aprovecha la API de Perritos para construir un sistema de búsqueda y visualización de una colección de imágenes de perros basado en una raza específica.
Para clonar el proyecto desde Github, abre tu terminal y ejecuta el siguiente comando:
Verás una salida similar a esta:
OutputCloning into 'doggy-directory'...
remote: Enumerating objects: 64, done.
remote: Counting objects: 100% (64/64), done.
remote: Compressing objects: 100% (48/48), done.
remote: Total 64 (delta 21), reused 55 (delta 15), pack-reused 0
Unpacking objects: 100% (64/64), 228.16 KiB | 3.51 MiB/s, done.
Cambia al directorio doggy-directory
:
Instala las dependencias del proyecto:
El comando npm install
instalará todas las dependencias del proyecto definidas en el archivo package.json
.
Después de instalar las dependencias, puedes ver la versión desplegada de la aplicación o puedes ejecutar la aplicación localmente con el siguiente comando:
Si eliges ejecutar la aplicación localmente, se abrirá en http://localhost:3000/
. Verás la siguiente salida en la terminal:
OutputCompiled successfully!
You can now view doggy-directory in the browser.
Local: http://localhost:3000
On Your Network: http://network_address:3000
Después de lanzarla, la página de inicio de la aplicación se verá así:
Las dependencias del proyecto han sido instaladas y la aplicación ahora se está ejecutando. A continuación, abre una nueva terminal y lanza las pruebas con el siguiente comando:
El comando npm test
inicia las pruebas en un modo interactivo de observación con Jest como su corredor de pruebas. Cuando está en modo de observación, las pruebas se ejecutan automáticamente después de que un archivo cambia. Las pruebas se ejecutarán cada vez que cambies un archivo y te informarán si ese cambio pasó las pruebas.
Después de ejecutar npm test
por primera vez, verás esta salida en la terminal:
OutputNo tests found related to files changed since last commit.
Press `a` to run all tests, or run Jest with `--watchAll`.
Watch Usage
› Press a to run all tests.
› Press f to run only failed tests.
› Press q to quit watch mode.
› Press p to filter by a filename regex pattern.
› Press t to filter by a test name regex pattern.
› Press Enter to trigger a test run.
Ahora que tienes la aplicación de ejemplo y el conjunto de pruebas en ejecución, puedes comenzar a probar la página de inicio.
Paso 2 — Probando la Página de Inicio
Por defecto, Jest buscará archivos con el sufijo .test.js
y archivos con el sufijo .js
en las carpetas __tests__
. Cuando hagas cambios en los archivos de prueba relevantes, serán detectados automáticamente. A medida que se modifican los casos de prueba, la salida se actualizará automáticamente. El archivo de prueba preparado para el proyecto de ejemplo doggy-directory
está configurado con código mínimo antes de agregar paradigmas de pruebas. En este paso, escribirás pruebas para verificar que la página de inicio de la aplicación se cargará antes de realizar una búsqueda.
Abre src/App.test.js
en tu editor para ver el siguiente código:
A minimum of one test block is required in each test file. Each test block accepts two required parameters: the first argument is a string representing the name of the test case; the second argument is a function that holds the expectations of the test.
Dentro de la función, hay un método render
que React Testing Library proporciona para renderizar tu componente en el DOM. Con el componente que deseas probar renderizado en el DOM del entorno de prueba, ahora puedes comenzar a escribir código para asegurar la funcionalidad esperada.
Agregarás un bloque de prueba al método render
que probará si la página de inicio se renderiza con precisión antes de realizar llamadas a la API o selecciones. Agrega el código resaltado debajo del método render
:
La función expect
se utiliza cada vez que deseas verificar un resultado específico, y acepta un solo argumento que representa el valor que produce tu código. La mayoría de las funciones expect
están emparejadas con una función de coincidencia para afirmar algo sobre un valor particular. Para la mayoría de estas afirmaciones, utilizarás coincidencias adicionales proporcionadas por jest-dom para facilitar la comprobación de aspectos comunes que se encuentran en el DOM. Por ejemplo, .toHaveTextContent
es la coincidencia para la función expect
en la primera línea, mientras que getByRole("heading")
es el selector para obtener el elemento del DOM.
React Testing Library proporciona el objeto screen
como una forma conveniente de acceder a las consultas pertinentes necesarias para afirmar contra el entorno de DOM de prueba. Por defecto, React Testing Library proporciona consultas que te permiten localizar elementos dentro del DOM. Hay tres categorías principales de consultas:
getBy*
(el más comúnmente utilizado)queryBy*
(utilizado cuando se prueba la ausencia de un elemento sin lanzar un error)findBy*
(utilizado cuando se prueba código asíncrono)
Cada tipo de consulta sirve a un propósito específico que se definirá más adelante en el tutorial. En este paso, te centrarás en la consulta getBy*
, que es el tipo de consulta más común. Para ver una lista exhaustiva de las diferentes variaciones de consulta, puedes revisar la hoja de trucos de consulta de React.
A continuación se muestra una imagen anotada de la página de inicio del Directorio de Perritos que indica cada sección que cubre la primera prueba (sobre la renderización de la página de inicio):
Cada función expect
está afirmando lo siguiente (mostrado en la imagen anotada anterior):
- Esperas que el elemento con el rol de encabezado tenga una coincidencia de subcadena de Directorio de Perritos.
- Esperas que la entrada select tenga un valor de visualización exacto de Selecciona una raza.
- Esperas que el botón Buscar esté desactivado ya que no se ha realizado una selección.
- Esperas que la imagen de marcador de posición esté presente en el documento ya que no se ha realizado una búsqueda.
Cuando hayas terminado, guarda el archivo src/App.test.js
. Como las pruebas se están ejecutando en modo de observación, los cambios se registrarán automáticamente. Si los cambios no se registran automáticamente, es posible que necesites detener y reiniciar el conjunto de pruebas.
Ahora, cuando veas tus pruebas en la terminal, verás la siguiente salida:
Output PASS src/App.test.js
✓ renders the landing page (172 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 2.595 s, estimated 5 s
Ran all test suites related to changed files.
Watch Usage: Press w to show more.
En este paso, escribiste una prueba inicial para verificar la vista de renderización inicial de la página de inicio de Doggy Directory. En el siguiente paso, aprenderás cómo simular una llamada a una API para probar código asíncrono.
Paso 3 — Simulando el Método fetch
En este paso, revisarás un enfoque para simular el método fetch
de JavaScript. Aunque hay numerosas formas de lograr esto, esta implementación utilizará los métodos spyOn
y mockImplementation
de Jest.
Cuando dependes de APIs externas, existe la posibilidad de que su API se caiga o tarde en devolver una respuesta. Simular el método fetch
proporciona un entorno consistente y predecible, dándote más confianza en tus pruebas. Un mecanismo de simulación de API es necesario para ejecutar correctamente pruebas que utilizan una API externa.
Nota: En un esfuerzo por mantener este proyecto simplificado, simularás el método fetch. Sin embargo, se recomienda utilizar una solución más robusta como Mock Service Worker (MSW) al simular código asíncrono para bases de código más grandes y listas para producción.
Abre src/mocks/mockFetch.js
en tu editor para revisar cómo funciona el método mockFetch
:
El método mockFetch
devuelve un objeto que se asemeja estrechamente a la estructura de lo que una llamada a fetch
devolvería en respuesta a las llamadas a la API dentro de la aplicación. El método mockFetch
es necesario para probar la funcionalidad asíncrona en dos áreas de la aplicación Doggy Directory: el menú desplegable que popula la lista de razas y la llamada a la API para recuperar imágenes de perros cuando se realiza una búsqueda.
Cierra src/mocks/mockFetch.js
. Ahora que entiendes cómo se usará el método mockFetch
en tus pruebas, puedes importarlo en tu archivo de prueba. La función mockFetch
se pasará como argumento al método mockImplementation
y luego se usará como una implementación falsa de la API fetch.
En src/App.test.js
, agrega las líneas resaltadas de código para importar el método mockFetch
:
Este código establecerá y desmontará la implementación simulada para que cada prueba comience desde un punto de partida equitativo.
jest.spyOn(window, "fetch");
crea una función simulada que seguirá las llamadas al método fetch
adjunto a la variable global window en el DOM.
.mockImplementation(mockFetch);
acepta una función que se utilizará para implementar el método simulado. Debido a que este comando anula la implementación original de fetch
, se ejecutará cada vez que se llame a fetch
dentro del código de la aplicación.
Una vez finalizado, guarda el archivo src/App.test.js
.
Ahora, al ver tus pruebas en la terminal, recibirás la siguiente salida:
Output console.error
Warning: An update to App inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */
This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act
at App (/home/sammy/doggy-directory/src/App.js:5:31)
18 | })
19 | .then((json) => {
> 20 | setBreeds(Object.keys(json.message));
| ^
21 | });
22 | }, []);
23 |
...
PASS src/App.test.js
✓ renders the landing page (429 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 1.178 s, estimated 2 s
Ran all test suites related to changed files.
La advertencia te indica que se produjo una actualización de estado cuando no se esperaba. Sin embargo, la salida también indica que las pruebas han simulado con éxito el método fetch
.
En este paso, has simulado el método fetch
e incorporado ese método en un conjunto de pruebas. Aunque la prueba está pasando, aún necesitas abordar la advertencia.
Paso 4 — Corrigiendo la Advertencia act
En este paso, aprenderás cómo corregir la advertencia act
que surgió después de los cambios en el Paso 3.
La advertencia act
ocurre porque has simulado el método fetch
, y cuando el componente se monta, hace una llamada a la API para obtener la lista de razas. La lista de razas se almacena en una variable de estado que llena el elemento option
dentro del input de selección.
La imagen a continuación muestra cómo se ve el input de selección después de que se realizó una llamada a la API exitosa para poblar la lista de razas:
La advertencia se genera porque el estado se establece después de que el bloque de prueba finaliza la representación del componente.
Para solucionar este problema, agrega las modificaciones resaltadas al caso de prueba en src/App.test.js
:
La palabra clave async
indica a Jest que el código asíncrono se ejecuta como resultado de la llamada a la API que ocurre cuando el componente se monta.
A new assertion with the findBy
query verifies that the document contains an option with the value of husky
. findBy
queries are used when you need to test asynchronous code that is dependent on something being in the DOM after a period of time. Because the findBy
query returns a promise that gets resolved when the requested element is found in the DOM, the await
keyword is used within the expect
method.
Una vez hecho esto, guarda los cambios realizados en src/App.test.js
.
Con las nuevas adiciones, verás que la advertencia de act
ya no está presente en tus pruebas:
Output PASS src/App.test.js
✓ renders the landing page (123 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 0.942 s, estimated 2 s
Ran all test suites related to changed files.
Watch Usage: Press w to show more.
En este paso, aprendiste cómo solucionar la advertencia de act
que puede ocurrir al trabajar con código asíncrono. A continuación, agregarás un segundo caso de prueba para verificar las funcionalidades interactivas de la aplicación Doggy Directory.
Paso 5 — Probando la función de búsqueda
En el paso final, escribirás un nuevo caso de prueba para verificar la función de búsqueda y visualización de imágenes. Utilizarás una variedad de consultas y métodos de API para lograr una cobertura de prueba adecuada.
Vuelve al archivo src/App.test.js
en tu editor. En la parte superior del archivo, importa la biblioteca complementaria user-event
y el método asincrónico waitForElementToBeRemoved
al archivo de prueba con los comandos resaltados:
Usarás estas importaciones más adelante en esta sección.
Después del método test()
inicial, agrega un nuevo bloque de prueba asincrónico y renderiza el componente App
con el siguiente bloque de código:
Con el componente renderizado, ahora puedes agregar funciones que verifiquen las características interactivas de la aplicación Doggy Directory.
Todavía en src/App.test.js
, agrega los bloques de código resaltados dentro del segundo método test()
:
La sección resaltada anteriormente simulará la selección de una raza de perro y verificará que se muestre el valor correcto.
La consulta getByRole
captura el elemento seleccionado y lo asigna a la variable select
.
Similar a cómo solucionaste la advertencia de act
en el Paso 4, utiliza la consulta findByRole
para esperar a que la opción cattledog
aparezca en el documento antes de continuar con más afirmaciones.
El objeto userEvent
importado anteriormente simulará interacciones comunes del usuario. En este ejemplo, el método selectOptions
selecciona la opción cattledog
que esperabas en la línea anterior.
La última línea asegura que la variable select
contiene el valor cattledog
seleccionado anteriormente.
La siguiente sección que agregarás al bloque de JavaScript test()
iniciará la solicitud de búsqueda para encontrar imágenes de perros basadas en la raza seleccionada y confirmará la presencia de un estado de carga.
Agrega las líneas resaltadas:
La consulta getByRole
localiza el botón de búsqueda y lo asigna a la variable searchBtn
.
El matcher toBeDisabled
de jest-dom verificará que el botón de búsqueda no esté deshabilitado cuando se realiza una selección de raza.
El método click
en el objeto userEvent
simula hacer clic en el botón de búsqueda.
La función waitForElementToBeRemoved
asincrónica importada anteriormente esperará la aparición y desaparición del mensaje Loading mientras la llamada a la API de búsqueda está en curso. queryByText
dentro del callback de waitForElementToBeRemoved
verifica la ausencia de un elemento sin lanzar un error.
La imagen a continuación muestra el estado de carga que se mostrará cuando una búsqueda esté en progreso:
A continuación, agregue el siguiente código Javascript para validar la imagen y mostrar el recuento de resultados:
La consulta getAllByRole
seleccionará todas las imágenes de perros y las asignará a la variable dogImages
. La variante *AllBy*
de la consulta devuelve un array que contiene varios elementos que coinciden con el rol especificado. La variante *AllBy*
difiere de la variante ByRole
, que solo puede devolver un elemento único.
La implementación simulada de fetch
contenía dos URL de imagen dentro de la respuesta. Con el comparador toHaveLength
de Jest, puedes verificar que se muestren dos imágenes.
La consulta getByText
verificará que aparezca el recuento adecuado de resultados en la esquina derecha.
Dos afirmaciones que utilizan los comparadores toHaveAccessibleName
verifican que el texto alternativo apropiado esté asociado con las imágenes individuales.
A completed search displaying images of the dog based on the breed selected along with the number of results found will look like this:
Cuando combines todas las piezas del nuevo código Javascript, el archivo App.test.js
se verá así:
Guarda los cambios realizados en src/App.test.js
.
Cuando revises tus pruebas, la salida final en la terminal ahora tendrá la siguiente salida:
Output PASS src/App.test.js
✓ renders the landing page (273 ms)
✓ should be able to search and display dog image results (123 ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 4.916 s
Ran all test suites related to changed files.
Watch Usage: Press w to show more.
En este paso final, agregaste una prueba que verifica las funcionalidades de búsqueda, carga y visualización de la aplicación Doggy Directory. Con la última afirmación escrita, ahora sabes que tu aplicación funciona.
Conclusión
A lo largo de este tutorial, escribiste casos de prueba utilizando Jest, React Testing Library y los comparadores jest-dom. Construyendo incrementalmente, escribiste pruebas basadas en cómo interactúa un usuario con la interfaz de usuario. También aprendiste las diferencias entre las consultas getBy*
, findBy*
y queryBy*
, y cómo probar código asíncrono.
Para obtener más información sobre los temas mencionados anteriormente, echa un vistazo a la documentación oficial de Jest, React Testing Library y jest-dom. También puedes leer el artículo de Kent C. Dodd sobre Errores Comunes con React Testing Library para aprender sobre las mejores prácticas al trabajar con React Testing Library. Para obtener más información sobre cómo usar pruebas de snapshot en una aplicación de React, consulta Cómo Escribir Pruebas de Snapshot.