Cómo construir aplicaciones React robustas con TDD y la Biblioteca de pruebas React

Una cosa con la que tuve problemas cuando comencé a aprender React fue probar mis aplicaciones web de una manera útil e intuitiva. Usé Enzyme with Jest para renderizar superficialmente un componente cada vez que quería probarlo.

Por supuesto, estaba abusando absolutamente de la función de prueba de instantáneas.

Bueno, al menos escribí una prueba ¿verdad?

Es posible que haya escuchado en alguna parte que la unidad de escritura y las pruebas de integración mejorarán la calidad del software que escribe. Tener malas pruebas, por otro lado, genera falsa confianza.

Recientemente, asistí a un taller a través de workshop.me con Kent C. Dodds, donde nos enseñó cómo escribir mejores pruebas de integración para aplicaciones React.

También nos engañó para que usáramos su nueva biblioteca de pruebas, a favor de su énfasis en probar la aplicación de la misma manera que un usuario la encontraría.

En este artículo, aprenderemos a ejercer TDD para construir aplicaciones React sólidas creando un feed de comentarios. Por supuesto, este proceso se aplica a casi todo el desarrollo de software, no solo a las aplicaciones React o JavaScript.

Empezando

Vamos a comenzar ejecutando create-react-app e instalando las dependencias. Supongo que si está leyendo un artículo sobre la prueba de aplicaciones, probablemente ya esté familiarizado con la instalación y puesta en marcha de proyectos JavaScript. Usaré hilo en lugar de npm aquí.

crear-reaccionar-comentario-aplicación de alimentación
comentarios de cd
hilo

Tal como está, podemos eliminar todos los archivos en el directorio src excepto index.js. Luego, justo dentro de la carpeta src, cree una nueva carpeta llamada componentes y otra carpeta llamada contenedores.

Para probar las utilidades, voy a compilar esta aplicación usando la Biblioteca de prueba React de Kent. Es una utilidad de prueba liviana que alienta al desarrollador a probar su aplicación de la misma manera que se usará.

Al igual que Enzyme, exporta una función de representación, pero esta función de representación siempre realiza un montaje completo de su componente. Exporta métodos auxiliares que le permiten localizar elementos por etiqueta o texto o incluso por ID de prueba. Enzyme también lo hace con su API de montaje, pero la abstracción que crea en realidad ofrece más opciones, muchas de las cuales le permiten escapar con detalles de implementación de prueba.

Ya no queremos probar los detalles de implementación. Queremos renderizar un componente y ver si suceden las cosas correctas cuando hacemos clic o cambiamos algo en la interfaz de usuario. ¡Eso es! No más comprobar directamente los accesorios o los nombres de estado o clase.

Vamos a instalarlos y ponernos a trabajar.

hilo agregar react-testing-library

Creación del feed de comentarios con TDD

Hagamos este primer componente al estilo TDD. Enciende tu corredor de prueba.

prueba de hilo --watch

Dentro de la carpeta de contenedores, vamos a agregar un archivo llamado CommentFeed.js. Junto a él, agregue un archivo llamado CommentFeed.test.js. Para la primera prueba, verifiquemos que los usuarios puedan crear comentarios. ¿Demasiado pronto? De acuerdo, dado que todavía no tenemos ningún código, comenzaremos con una prueba más pequeña. Verifiquemos que podamos procesar el feed.

Algunas notas sobre react-testing-library

Primero, observemos la función de renderizado aquí. Es similar a la forma en que react-dom representa un componente en el DOM, pero devuelve un objeto que podemos desestructurar para obtener algunos ayudantes de prueba. En este caso, obtenemos queryByText, que, dado algún texto que esperamos ver en el DOM, devolverá ese elemento HTML.

Los documentos de React Testing Library tienen una jerarquía que debería ayudarlo a decidir qué consulta u método obtener. En general, el orden es el siguiente:

  • getByLabelText (entradas de formulario)
  • getByPlaceholderText (solo si su entrada no tiene una etiqueta, ¡menos accesible!)
  • getByText (botones y encabezados)
  • getByAltText (imágenes)
  • getByTestId (use esto para cosas como texto dinámico o elementos extraños que desee probar)

Cada uno de estos tiene un queryByFoo asociado que hace lo mismo, excepto que no fallará su prueba cuando no encuentre un elemento. Úselos si solo está probando la existencia de un elemento.

Si ninguno de estos le proporciona exactamente lo que está buscando, el método de representación también devuelve el elemento DOM asignado a la propiedad del contenedor, por lo que puede usarlo como container.querySelector ('body #root').

El primer código de implementación

Ahora, la implementación se verá bastante simple. Solo necesitamos asegurarnos de que "Comentario Feed" esté en el componente.

Podría ser peor, quiero decir, estaba a punto de escribir todo este artículo mientras diseñaba los componentes. Afortunadamente, las pruebas no se preocupan demasiado por los estilos, por lo que podemos centrarnos en nuestra lógica de aplicación.

Esta próxima prueba verificará que podamos hacer comentarios. Pero ni siquiera tenemos comentarios, así que agreguemos ese componente también. Después de la prueba sin embargo.

También voy a crear un objeto de utilería para almacenar los datos que podemos reutilizar en estas pruebas.

En este caso, estoy verificando que la cantidad de comentarios sea igual a la cantidad de elementos pasados ​​al comentario. Es trivial, pero el fracaso de la prueba nos da la oportunidad de crear el archivo Comment.js.

Esta luz verde ilumina nuestro conjunto de pruebas para que podamos proceder sin temor. Todos saludan a TDD, el salvador de nuestra especie. Funciona cuando le damos una matriz vacía, por supuesto. Pero, ¿y si le damos algunos objetos reales?

Debemos actualizar nuestra implementación para realmente renderizar cosas. Bastante simple ahora que sabe a dónde vamos, ¿verdad?

Ah mira eso, nuestra prueba una vez más está pasando. Aquí hay una buena foto de su belleza.

¿Te das cuenta de que nunca dije que deberíamos iniciar nuestro programa con el inicio de hilo? Lo mantendremos así por un tiempo. El punto es que debes sentir el código con tu mente.

El estilo es lo que está afuera, es lo que está adentro lo que cuenta.

Sin embargo, en caso de que desee iniciar la aplicación, actualice index.js a lo siguiente:

Añadir formulario de comentarios

Aquí es donde las cosas comienzan a ser más divertidas. Aquí es donde pasamos de verificar soñolientamente la existencia de nodos DOM a hacer cosas con eso y validar el comportamiento. Todas esas otras cosas fueron un calentamiento.

Comencemos describiendo lo que quiero de este formulario. Debería:

  • contener una entrada de texto para el autor
  • contener una entrada de texto para luego comentar
  • tener un botón de enviar
  • eventualmente llame a la API o cualquier servicio que se encargue de crear y almacenar el comentario.

Podemos eliminar esta lista en una única prueba de integración. Para los casos de prueba anteriores, lo tomamos con bastante lentitud, pero ahora vamos a acelerar el ritmo e intentar clavarlo de un solo golpe.

¿Observa cómo se está desarrollando nuestro conjunto de pruebas? Pasamos de los accesorios de codificación dentro de sus propios casos de prueba a crear una fábrica para ellos.

Organizar, actuar, afirmar

La siguiente prueba de integración se puede dividir en tres partes: organizar, actuar y afirmar.

  • Organizar: crear accesorios y otros accesorios para el caso de prueba
  • Actuar: simule cambios en los elementos, como entradas de texto o clics de botones
  • Afirmar: afirmar que las funciones deseadas se invocaron el número correcto de veces y con los argumentos correctos

Se hacen algunas suposiciones sobre el código, como el nombre de nuestras etiquetas o el hecho de que tendremos un accesorio createComment.

Al buscar entradas, queremos intentar encontrarlas por sus etiquetas. Esto prioriza la accesibilidad cuando estamos creando nuestras aplicaciones. La forma más fácil de obtener el formulario es mediante container.querySelector.

A continuación, debemos asignar nuevos valores a las entradas y simular el cambio para actualizar su estado. Este paso puede parecer un poco extraño, ya que normalmente escribimos un carácter a la vez, actualizando el estado del componente para cada nuevo personaje.

Esta prueba se comporta más como el comportamiento de copiar / pegar, pasando de una cadena vacía a "Sócrates". No hay problemas importantes por ahora, pero es posible que deseemos tomar nota de eso en caso de que surja más adelante.

Después de enviar el formulario, podemos hacer afirmaciones sobre cosas como qué accesorios se invocaron y con qué argumentos. También podríamos usar este momento para verificar que las entradas del formulario se borraron.

¿Es intimidante? No hay que temer, hija mía, camina por aquí. Comience agregando el formulario a su función de representación.

Podría dividir esta forma en su propio componente separado, pero me abstendré por ahora. En cambio, lo agregaré a mi "Lista de deseos del refactorista" que mantengo al lado de mi escritorio.

Este es el camino de TDD. Cuando algo parezca que se puede refactorizar, anótelo y continúe. Refactorice solo cuando la presencia de una abstracción lo beneficie y no se sienta innecesario.

¿Recuerdas cuando reestructuramos nuestro conjunto de pruebas creando la fábrica createProps? Así. También podemos refactorizar las pruebas.

Ahora, agreguemos los métodos de clase handleChange y handleSubmit. Estos se disparan cuando cambiamos una entrada o enviamos nuestro formulario. También voy a inicializar nuestro estado.

Y eso lo hizo. Nuestras pruebas están pasando y tenemos algo que se parece a una aplicación real. ¿Cómo se ve nuestra cobertura?

No está mal. Si ignoramos todas las configuraciones que van dentro de index.js, tenemos una aplicación web completamente cubierta con respecto a las líneas ejecutadas.

Por supuesto, probablemente hay otros casos que queremos probar para verificar que la aplicación esté funcionando según lo previsto. Ese número de cobertura es algo de lo que su jefe puede presumir cuando están hablando con las otras cohortes.

Me gusta comentarios

¿Qué tal si comprobamos que nos puede gustar un comentario? Este puede ser un buen momento para establecer algún concepto de autenticación dentro de nuestra aplicación. Pero todavía no saltaremos demasiado lejos. Primero, actualice nuestra fábrica de accesorios para agregar un campo de autenticación junto con los ID de los comentarios que generamos.

El usuario que está "autenticado" tendrá su propiedad de autenticación transmitida a través de la aplicación. Se anotará cualquier acción que sea relevante para verificar si están autenticados.

En muchas aplicaciones, esta propiedad puede contener algún tipo de token de acceso o cookie que se envía al realizar solicitudes al servidor.

En el cliente, la presencia de esta propiedad le permite a la aplicación saber que puede permitir que el usuario vea su perfil u otras rutas protegidas.

En este ejemplo de prueba, sin embargo, no vamos a jugar demasiado con la autenticación. Imagine un escenario como este: cuando ingresa a una sala de chat, le da su nombre de usuario. A partir de ese momento, usted está a cargo de todos los comentarios que usan este nombre de pantalla, a pesar de quién más inició sesión con ese nombre.

Si bien no es una gran solución, incluso en este ejemplo artificial, solo nos preocupa probar que el componente CommentFeed se comporte como debería. No nos preocupa cómo inician sesión nuestros usuarios.

En otras palabras, podemos tener un componente de inicio de sesión totalmente diferente que maneja la autenticación de un usuario en particular, enviándolos a través de aros de fuego y furia para derivar la poderosa propiedad de autenticación que les permite causar estragos en nuestra aplicación.

Hagamos un "me gusta" en un comentario. Agregue este próximo caso de prueba y luego actualice la fábrica de accesorios para incluir likeComment.

Y ahora para la implementación, comenzaremos actualizando el componente Comentario para que tenga un botón Me gusta, así como un atributo de testid de datos para que podamos localizarlo.

Puse el ID de prueba directamente en el botón para que podamos simular inmediatamente un clic en él sin tener que anidar los selectores de consulta. También adjunté un controlador onClick al botón para que llame a la función onLike que se le pasó.

Ahora solo agregamos este método de clase a nuestro CommentFeed:

Quizás se pregunte por qué no simplemente pasamos el accesorio likeComment directamente al componente Comentario. ¿Por qué lo hacemos una propiedad de clase?

En este caso, debido a que es bastante simple, no tenemos que construir esta abstracción. En el futuro, podemos decidir agregar otros controladores onClick que, por ejemplo, manejen eventos analíticos o inicien una suscripción a los comentarios futuros de esa publicación.

Poder agrupar múltiples llamadas a funciones diferentes en el método handleLike de este componente contenedor tiene sus ventajas. También podríamos usar este método para actualizar el estado del componente después de un “Me gusta” exitoso si así lo elegimos.

Comentarios desagradables

En este punto tenemos pruebas de trabajo para renderizar, crear y recibir comentarios. Por supuesto, no hemos implementado la lógica que realmente hace eso: no estamos actualizando la tienda ni escribiendo en una base de datos.

También puede notar que la lógica que estamos probando es frágil y no es terriblemente aplicable a un feed de comentarios del mundo real. Por ejemplo, ¿qué pasa si tratamos de recibir un comentario que ya nos gustó? ¿Incrementará el conteo indefinido de me gusta, o será diferente? ¿Me pueden gustar mis propios comentarios?

Dejaré extender la funcionalidad de los componentes a su imaginación, pero un buen comienzo sería escribir un nuevo caso de prueba. Aquí hay uno que se basa en la suposición de que nos gustaría implementar un comentario que no nos gusta:

Tenga en cuenta que este feed de comentarios que estamos creando me permite que me gusten mis propios comentarios. Quien hace eso

He actualizado el componente Comentario con cierta lógica para determinar si al usuario actual le ha gustado o no el comentario.

Bueno, hice un poco de trampa: antes de pasar al autor a la función onLike, cambié a currentUser, que es el accesorio de autenticación transmitido al componente Comentario.

Después de todo, no tendría sentido que el autor del comentario apareciera cuando a alguien más le gusta su comentario.

Me di cuenta de esto porque estaba escribiendo pruebas vigorosamente. ¡Si hubiera estado codificando por coincidencia, esto podría haber pasado de largo hasta que uno de mis compañeros de trabajo me reprendió por mi ignorancia!

Pero aquí no hay ignorancia, solo pruebas y el código que sigue. Asegúrese de actualizar CommentFeed para que espere transmitir la propiedad de autenticación. Para los controladores onClick, podemos omitir pasar alrededor de la propiedad auth, ya que podemos derivar eso de la propiedad auth en los métodos handleLike y handleDislike de los padres.

Terminando

Con suerte, su conjunto de pruebas parece un árbol de Navidad apagado.

Hay tantas rutas diferentes que podemos tomar en esto, puede ser un poco abrumador. Cada vez que tenga una idea para algo, simplemente escríbala, ya sea en papel o en un nuevo bloque de prueba.

Por ejemplo, supongamos que realmente desea implementar handleLike y handleDislike en un método de clase única, pero tiene otras prioridades en este momento. Puede hacer esto documentando en un caso de prueba así:

Esto no significa que deba escribir una prueba completamente nueva. También puede actualizar los dos casos anteriores. Pero el punto es que puede usar su corredor de prueba como una lista de tareas pendientes más imprescindible para su aplicación.

Enlaces Útiles

Hay algunas grandes piezas de contenido por ahí que se ocupan de las pruebas en general. Aquí hay algunos en particular que inspiraron este artículo, así como mis propias prácticas.

  • "Presentación de la Biblioteca de pruebas de reacción" de Kent C. Dodds. Es una buena idea comprender la filosofía detrás de esta biblioteca de pruebas.
  • "Software Testing Anti-patterns" de Kostis Kapelonis. Un artículo extremadamente detallado que analiza las pruebas de unidad e integración. También cómo no hacerlas.
  • "Test Driven Development by Example" de Kent Beck. Este es un libro físico que analiza los patrones de TDD. No es demasiado largo y está escrito de manera conversacional, por lo que es fácil de digerir.

Espero que te ayude por un tiempo.

¿Tienes curiosidad por más publicaciones o comentarios ingeniosos? Si te ha gustado este artículo, ¡dame un aplauso y sígueme en Medium, Github y Twitter!