
TDD en JavaScript I. Usando Jest
Estos días quiero presentaros una serie de proyectos y miniapuntes que realicé con mayor o menor acierto para explicar el despliegue seguro de las aplicaciones web. En estos miniproyectos hablaré del TDD y BDD y de cómo realizar pruebas unitarias, de integración o E2E. Además, mostraré en esta serie de entradas cómo manejar librerías específicas para ello, como pueden ser Jest o Cypress. En esta primera parte nos centraremos en el uso de Jest, especialmente para test unitarios y de integración y cómo usarlo en proyectos Node.js o de Vue.js.
Principios del TDD
Automatizar los test unitarios y poder trabajar con ellos nos ofrece iniciar las bases del TDD (Test-Driven Development). El desarrollo guiado por pruebas de software, o Test-driven development (TDD), es una práctica de ingeniería de software que involucra otras dos prácticas: escribir las pruebas primero (Test First Development) y refactorización (Refactoring). Para ello:
- Crea el test.
- Comprueba que falla.
- Escribe el código justo para pasarlo.
- Comprueba que funciona.
- Refactoriza.
- Vuelve al primer paso.
- Y disfrutar 🙂.

El propósito del desarrollo guiado por pruebas es lograr un código limpio que funcione. La idea es que los requisitos sean traducidos a pruebas; de este modo, cuando las pruebas pasen, se garantizará que el software cumple con los requisitos que se han establecido. Para ello debemos trabajar en requisitos o en partes de las historias de usuario.
ATDD (Acceptance Test Driven Development)
Los tests de aceptación o de cliente son el criterio escrito de que el software cumple los requisitos de negocio que el cliente demanda. Los requisitos se traducen por ejemplos ejecutables (de cómo se ejecuta una funcionalidad con sus entradas y salidas esperadas) surgidos del consenso entre los distintos miembros del equipo, incluido, por supuesto, el cliente. Una vez que tenemos los ATDD, se crea el test que lo representa y, posteriormente, iniciamos TDD; de esta manera, el código que pasa el test se asegura que cumple con el requisito a conseguir.

Jest
Jest es una de las muchas posibilidades que tenemos para testear nuestro código o proyecto en JavaScript (ya sea en cliente o en Node.js). Jest está basado en Jasmine, y se define como la suite de "test con 0 configuración"; es decir, mientras otras suites de test necesitan de un motor (test runner) para pasar los test y de la propia suite de test, así como de una librería de aserciones o matchers, Jest intenta que todo esto esté ya agrupado para agilizar el proceso de test desde el principio. Esto no quiere decir que no se pueda ampliar o profundizar y personalizar con otras librerías o que no tenga la potencia de otros.
En cualquier caso, las bases de estos ejemplos te servirán para las distintas alternativas existentes.

Otras alternativas
Existen muchas alternativas y cada una enfocada a un aspecto. En este tutorial me centro en Jest, pero como he dicho hay varias, ya sea para test unitarios, TDD, BDD o E2E. Te recomiendo este artículo para tener una visión al respecto. Remarco los siguientes para test unitarios:
- Jasmine. Es una de las librerías por excelencia para hacer test, "padre" de Jest y, además, la suite básica en Angular.
- Karma. Es un motor de test, desarrollado por el equipo de Angular, que suele usarse junto a Jasmine para este tipo de proyectos.
- Mocha. Es una librería de test pensada sobre todo para aplicaciones Node.js, muy potente y configurable al gusto.
- Chai. Es una librería de aserciones generalmente usada con Mocha para dar potencia a nuestros matchers en nuestros tests.
Proyectos y repositorios
Esta entrada toma como punto de partida el siguiente proyecto:
Además, ideas similares a las mostradas puedes encontrarlas aplicadas en los siguientes proyectos:
Instalación
Es importante seguir la documentación oficial.
npm install --save-dev jestOtras configuraciones
Otras configuraciones realizadas las tienes en package.json.
Ejecutando el test
Debes tener un directorio llamado tests, y en él ficheros .spec.test.
npm run testMatchers
Los Matchers nos permiten comparar de diferente manera valores esperados con los obtenidos. Podemos hacerlo de la siguiente manera, aunque hay más:
Igualdad
.toBe: usado para comparar valores primitivos..toEqual: usado para comparar recursivamente todas las propiedades de un objeto, también conocido como igualdad profunda.
Numéricos
.toBeLessThan: el valor es menor que..toBeLessThanOrEqual: el valor es menor o igual que..toBeGreaterThanOrEqual: el valor es mayor o igual que..toBeGreaterThan: el valor es mayor que.
Boolean, Nulos y Undefined
.toBeTruthy: el valor es verdadero..toBeFalsy: el valor es falso..toBeUndefined: el valor es 'undefined'..toBeNull: el valor es 'null'.
Arrays y contenido
.toContain: contiene el elemento dentro del array..toHaveLength: el array tiene la longitud.
Strings
.toMatch: comprueba que un texto coincide con una expresión regular..toHaveLength: comprueba la longitud.- Podemos usar otros anteriores.
Cobertura
Podemos saber cuánto hemos testeado nuestro código realizando un análisis de cobertura. Jest nos ofrece el flag --coverage en la línea de comandos para comprobar la cobertura de nuestros test.
Informe de cobertura consola
npm run test:coverage
jest --coverage
PASS tests/funciones.spec.js
--------------|---------|----------|---------|---------|-------------------
| File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
| -------------- | --------- | ---------- | --------- | --------- | ------------------- |
| All files | 100 | 100 | 100 | 100 |
| arrays.js | 100 | 100 | 100 | 100 |
| boolean.js | 100 | 100 | 100 | 100 |
| funciones.js | 100 | 100 | 100 | 100 |
| persona.js | 100 | 100 | 100 | 100 |
| strings.js | 0 | 0 | 0 | 0 |
| -------------- | --------- | ---------- | --------- | --------- | ------------------- |
Test Suites: 3 passed, 3 total
Tests: 23 passed, 23 total
Snapshots: 0 total
Time: 1.703 s
Ran all test suites.Informe de cobertura web
Está en la carpeta coverage creada, en coverage/lcov-report/index.html.
Test detectando cambios
Jest nos ofrece el flag --watchAll para que esté escuchando constantemente los cambios y pueda hacer las pruebas sobre los cambios realizados y no estar ejecutando el script constantemente. npm run test:watch
jest --watchAllPrecondiciones y postcondiciones de test
Muchas veces queremos tener una serie de condiciones a ejecutar antes o después de cada test o de cada conjunto de test. Podemos ponerlo incluso en cada suite o conjunto de describe.
afterEach(): después de cada prueba.afterAll(): después de todas las pruebas.beforeEach(): antes de cada prueba.beforeAll(): antes de todas las pruebas.
Testing asíncrono
Cuando necesitamos realizar test con funciones asíncronas, Jest nos ofrece varias opciones usando callbacks, promesas y async / await. De esta manera podemos testear, por ejemplo, el consumo de APIs REST y ya usar métodos anteriores para saber si lo que recibimos es correcto.
Testing API REST
Otra de las cosas que podemos hacer es testear nuestra propia API REST o APIs REST remotas, o simularlas en base a mocks. Para ello vamos a hacer uso de la librería Supertest. También podríamos usar Axios, como hemos visto en los apartados anteriores.
Para testear más rápido podemos usar ficheros mock locales.
Testing DOM
Podemos testear nuestro DOM con Jest usando Vanilla JS o JQuery; incluso podemos hacer uso de la librería Jest-DOM. Es recomendable que sepamos pasar la parte de la web y la lógica que queramos testear. Jest viene con jsdom, que simula un entorno DOM como si estuviera en el navegador. Esto significa que cada API DOM que llamamos se puede observar de la misma manera que se observaría en un navegador. Un ejemplo de testear un ejemplo de DOM con Vanilla JS lo tienes en todolist.js.
Jest-DOM
Jest-DOM es una librería que extiende Jest para usarla para testear tus aplicaciones HTML con una serie de Matchers especializados para esta labor, por si queremos usarlo.
Snapshot testing
Los snapshots nos garantizan que no vaya a ocurrir algún cambio inesperado en nuestra UI. Comprobamos los datos que tenemos con lo que estamos trayendo y que no deben cambiar, ya que esto lo usamos para casos en donde algún dato en particular muy rara vez cambiará.
En caso de que queramos aceptar el cambio, añadiremos el parámetro -u.
Una vez ejecutado el test con snapshot, este nos creará una carpeta con el nombre __snapshots__. Este fichero es una captura de los datos que le pasamos en el fichero. La primera vez que ejecutamos el test crea esa captura que se usará para validar. El resto de las veces comparará y, si hay cambios, dará error.
Si queremos crear una excepción, añadiremos las excepciones dentro del método .toMatchSnapshot(). Es importante que cuando ejecutemos el test con las excepciones usemos el flag -u para que use los nuevos cambios si ya existiese una captura previa.
Ejemplos con Jest
Ejemplo Contactos
Puedes ver este ejemplo en contactos.js, donde primero se hicieron los test y luego se hizo una implementación que, además, presenta distintas alternativas.
Ejemplo Cuenta
Además, puedes seguir el ejemplo en src/cuenta, donde se ha desarrollado un ejemplo completo siguiendo TDD y ATDD, donde puedes seguirlo commit a commit.
Mocks en TDD
También podemos hacer uso de Mocks para crear los test desde el comienzo y saber si están correctos y, con estos mocks, posteriormente crear nuestro código para que los pase. Recuerda que un Mock es un objeto preprogramado con expectativas que conforman la especificación de cómo se espera que se reciban las llamadas. Puedes verlo en el fichero mocks.
Testeando un framework: Vue.js
En este caso vamos a ver cómo testear un framework como es Vue.js con Jest. Es importante remarcar que cada framework tiene sus propias particularidades y debemos leer bien su documentación sobre cómo hacerlo. En nuestro caso, usaremos Vue.js y su suite de herramientas de testeo que pueden usar Jest como framework, conocidas como Vue Test Utils. Hemos usado como ejemplo el proyecto en src/vue-testing.

Más información
Puedes obtener más información en esta entrada de mi web:
Testeando en Vue.js con Jest y Cypress
Configurando Jest con Vue CLI
Nos centraremos en ejecutar el comando test:unit para ejecutar nuestros test unitarios. Para ello, crearemos el proyecto con Vue CLI, indicando en la configuración manual el uso de test unitarios.
Please pick a preset: Manually select features
? Check the features needed for your project: Choose Vue version, Babel, Linter, Unit, E2E
? Choose a version of Vue.js that you want to start the project with 2.x
? Pick a linter / formatter config: Airbnb
? Pick additional lint features: Lint on save
? Pick a unit testing solution:
Mocha + Chai
❯ JestEjemplo para ejecutar los test:
npm run test:unitIremos aplicando TDD. Puedes seguir el proyecto commit a commit.
Montando componentes
Para realizar el montaje del componente para nuestra prueba usamos la siguiente línea: const wrapper = mount(XXXX), siendo XXXX el componente a montar.
En este momento, si pasamos el test se mostrará como correcto, pero es que realmente todavía no hemos realizado ninguna comprobación. Para que los test puedan comprobar el funcionamiento necesitamos realizar una afirmación (assertions; en Jest es expect) y aplicarle un match.
Debemos tener en cuenta que podemos montar los componentes de distintas maneras:
shallowMount: monta el componente sin sus hijos.mount: monta el componente con sus hijos.
De esta manera tenemos el Wrapper de nuestro componente y podemos trabajar con él siguiendo la guía de testing. De hecho, dentro del objeto vm podemos acceder a todos los datos y funcionalidad del componente.
Debemos destacar que wrapper es un mock de la instancia de Vue.
Si nos fijamos, podemos ver que el padre que contiene un hijo, cuando usamos shallowMount, nos ha montado una especie de componente "falso" (<child-component-stub></child-component-stub>) que sustituye al real. Esto nos puede resultar muy útil cuando necesitamos aislar los test que tenemos que realizar sobre el padre y no necesitamos a sus hijos.
Test sobre las propiedades
Para realizar los test sobre las propiedades de nuestros componentes, vue-test-utils ofrece tanto para mount como shallowMount un segundo parámetro llamado propsData.
Además de propsData, también podemos usar setProps para añadir propiedades (pero este es asíncrono y debemos usar async/await).
Test sobre propiedades computadas
Los test sobre las propiedades computadas son muy sencillos, ya que únicamente deberemos llamarlas como lo haríamos en nuestro código.
Testeando métodos y eventos
Los eventos de un componente pueden llamar a métodos; para ello, los llamamos igual que a un método normal y comprobamos los resultados, o llamamos con el método trigger al evento disparado.
Accediendo a elementos del DOM o específicos
Podemos acceder a cada elemento del componente usando la función find y usando los selectores de CSS:
- tag selectores (
div,foo,bar). - class selectores (
.foo,.bar). - attribute selectores (
[foo],[foo="bar"]). - id selectores (
#foo,#bar). - pseudo selectores (
div:first-of-type).
En nuestro caso, usaremos la propiedad de Vue :data-testid para nombrar inequívocamente los elementos que queramos/usaremos para testear y referenciarlos con el selector CSS.
Ejemplos de Vue.js y Jest
En el proyecto Vue que tienes existen ejemplos de cada cosa indicada:
HelloWorldy cómo testear un componente básico enexample.spec.js.TheHeader,ParentyChilden conTestingVuepara ver las diferentes formas de testear a nivel básico y cómo hacer wrapper de los componentes.Final.vue, donde se testean propiedades, datos, eventos y métodos.ToDo.vue, el cual nos sirve para testear un típico gestor de tareas y trabajar con selectores del tipo id, así como mocks con Jest en Vue. De hecho, se ha hecho un mock de axios, de manera que siempre que llamemos a una función que usa axios se simula dicha función usando el mock; es decir, se simula su comportamiento con los datos que tenemos en la carpeta__mocks__. Jest recogerá automáticamente este archivo y mapeará las llamadas que se hacen a la biblioteca axios por las llamadas a nuestro fichero en el test, ahorrándonos el consumo de la API REST externa y mejorando el rendimiento de nuestros test.
Introducción al Testing E2E con Jest
Los tests E2E simulan el comportamiento de un usuario real. Prueban toda la aplicación de principio a fin, cubriendo así secciones que las pruebas unitarias y las pruebas de integración no cubren, simulando acciones del usuario sobre la interfaz y sus componentes a nivel de conjunto.
Aunque hay frameworks específicos para hacer este tipo de tareas, como es el conocido Cypress.io (del cual hablaremos en la segunda parte de este especial), nosotros estamos trabajando con el motor Jest y queremos algo que nos simplifique esto y además con apenas configuración. Es por ello que vamos a hacer uso de Puppeteer para este menester.

Acciones
Con esta librería podemos hacer:
- Analizar el SEO de la página y obtener sus atributos.
- Modificar el viewport y capturar la pantalla.
- Utilizar selectores.
- Disparar eventos.
- Interactuar con los elementos.
¿Cómo hacer el E2E?
Partimos de los test de aceptación y automatizamos el flujo de acciones que el usuario haría y queremos probar.
Ejemplos de Jest con Puppeteer
En la carpeta puppeteer tienes unos cuantos; además, puedes encontrar muchos más en internet, pero te recomiendo este y este. Eso sí, no olvides que ya debes usarlo dentro del entorno de Jest y siguiendo su filosofía.
Conclusiones
TDD solo es una metodología de trabajo. Hacer test e intentar asegurar que tu código no falla debe ser una obligación o una parte más de tu labor como desarrollador. Deberías tomar por costumbre hacerlo, independientemente del lenguaje/framework que uses. Es una buena práctica y debes hacerlo. Puede que sea divertido, puede que no, pero te aseguro que aprenderás mucho y encontrarás nuevas formas de programar y desarrollar código, y con ello mejorarás como desarrollador/a.
Proyectos y repositorios para iniciarse con Jest
