¿No te ha pasado que haces algún cambio en tu código JavaScript y un tiempo después te das cuenta que algo ha dejado de funcionar? ¿hay alguna manera de evitarlo? Sí, unit tests. ¿Y en JavaScript? Sí, Mocha.

Unit tests

Todos los que alguna vez hemos desarrollado nos encontramos con situaciones de este tipo. Intentas arreglar un bug, o tratas de implementar una pequeña funcionalidad, y de repente otras partes dejan de funcionar. La manera de evitarlo es usar unit tests: pruebas unitarias que comprueban que un pequeño módulo de código (generalmente un método) funciona correctamente.

Existe una metodología de desarrollo basada en esto: TDD (Test-driven development). O lo que es lo mismo, desarrollo guiado por pruebas. Idealmente consiste en crear los unit tests que deberá pasar nuestro software al comienzo del desarrollo. Aunque en la práctica, muchas veces se implementan después de tener el desarrollo acabado. Con esto creamos unit tests “viciados por el desarrollo”, y adaptados para que funcionen con el software que ya está creado. Aún así, pueden ser útiles para evitar que futuros cambios rompan la funcionalidad existente. Pero como ya estarás pensando, si empezamos la implementación por los unit tests, nos aseguraremos que nuestro software terminará cumpliendo unas funcionalidades mínimas. Además evitaremos que futuros cambios rompan algo que ya funcionaba.

Unit tests en JavaScript

En JavaScript, ¿existen herramientas para programar unit tests? Sí, existen multiples herramientas que permiten desarrollo guiado por unit test. En el enlace podéis ver las características de varias de ellas, con sus pros y sus contras. A la hora de decantarme por una de estas herramientas, elegí Mocha, porque es muy sencilla de usar, y además, su flexibilidad permite agregar multiples librerías que la hacen tan potente como queramos. Aún así, en su versión básica Mocha es más que suficiente para crear unit tests que prueben métodos estándar.

Mocha

Mocha está basado en Nodejs, por lo que necesitamos tenerlo instalado (instalar Nodejs).

Para instalar Mocha:

npm install mocha -g

Para ejecutar Mocha en nuestro proyecto:

mocha

Fácil ¿verdad?

Ahora vamos a implementar nuestro primer unit test. Dentro de nuestro proyecto creamos una carpeta test. En ella meteremos todos los ficheros .js (JavaScript) con los unit tests. Mocha por defecto buscará esta carpeta y ejecutará los ficheros que haya dentro.

var assert = require("assert"); describe("Numbers",function(){ it('should add two numbers',function(){ assert.equal(5, 3 + 2); }) });

Si lanzamos mocha en la raíz de nuestro proyecto veremos el resultado de nuestro unit test.

Este es un ejemplo muy sencillo, en el que comprobamos que el resultado de una suma es correcta. Todavía no estamos llamando a ningún metodo que hayamos implementado en nuestro proyecto JavaScript. Pero nos sirve para ver la sintáxis básica.

Sintáxis de Mocha

Con assert vamos a poder hacer comprobaciones de valores, para asegurar que obtenemos los valores esperados cuando llamamos a una función.

vamos a poder hacer comprobaciones de valores, para asegurar que obtenemos los valores esperados cuando llamamos a una función. describe : Nos permitirá agrupar unit tests en secciones. Pueden anidarse unos describe dentro de otros, para crear subsecciones de unit tests.

: Nos permitirá agrupar unit tests en secciones. Pueden anidarse unos describe dentro de otros, para crear subsecciones de unit tests. it: Encapsula la implementación de un unit test

Otras funciones útiles:

before : Este método irá dentro de una sección describe, y se ejecutará antes que ninguno de los unit tests (it) de la sección. Nos servirá para inicializar variables necesarias para la ejecución de los unit tests.

: Este método irá dentro de una sección describe, y se ejecutará antes que ninguno de los unit tests (it) de la sección. Nos servirá para inicializar variables necesarias para la ejecución de los unit tests. after : Se ejecutará después de todos los unit tests de la sección. Podremos utilizarlo para dejar el sistema en el estado en el que estaba antes de ejecutar los unit tests de la sección.

: Se ejecutará después de todos los unit tests de la sección. Podremos utilizarlo para dejar el sistema en el estado en el que estaba antes de ejecutar los unit tests de la sección. También existen beforeEach y afterEach que se ejecutarán antes y después de cada unit test de la sección.

Una librería muy utilizada sobre Mocha, es la librería chai.

npm install chai -g

Chai nos da alguna utilidad más a la hora de comprobar valores. Para ello sobreescribe el método assert añadiendo algunas posibilidades, como comprobar que un valor es mayor o menor que uno dado, o que un objeto no es null.

Unit tests asíncronos

Uno de los problemas que suele surgir creando unit tests en JavaScript, es al crear unit tests asíncronos. En este caso, Mocha no puede saber que el test es asíncrono y cuando ha terminado de probarse o cuando un método before o after ha terminado de ejecutarse. Para solucionar este problema, podemos llamar al método callback que se pasa como parámetro de la función, que generalmente se nombra como “done”

Vamos a ver un ejemplo más completo, con métodos before y after. Además, utilizaremos métodos asíncronos para inicializar la base de datos y para resetearla después. Esto nos obligará a usar el callback done para indicarle a Mocha cuando ha terminado cada paso.

Imaginemos que tenemos un colección en mongo llamada channels, en la que guardamos canales con un nombre. Y tenemos un fichero JavaScript encargado del acceso a esta colección. En esta sección de unit tests queremos probar los métodos get que obtienen objetos Channel de esa colección. Para ello tenemos que importar el fichero que queremos probar (channels.js).

Usaremos el método before para asegurarnos que en la colección al menos tenemos 3 documentos Channel. Como el salvado en Mongo está implementado de manera asíncrona (además de que usamos el método forEach que también es asíncrono), tenemos que hacer uso del callback done para decirle a Mocha cuando hemos terminado de hacer el proceso de inicializado. En el método after realizaremos el proceso contrario, borrando los 3 documentos para dejar la base de datos en el estado inicial. Y en los 2 métodos it probamos las funciones get que obtienen todos los documentos de la colección, y comprobamos que al menos haya 3 (assert.isAtLeast) y el get que obtiene sólo uno por su id, y comprobamos que no sea null (assert.isNotNull)

var assert = require('chai').assert; var channelsDB = require('../db/channels'); var channelNamesArray = ['test channel 1', 'test channel 2', 'test channel 3']; describe('Channels', function () { describe('#get()', function () { var channelIds = []; // Create 3 channels in DB before(function (done) { var numChannelsCreated = 0; channelNamesArray.forEach(function (channelName) { var channel = new channelsDB.Channel({name: channelName}) channel.save(function (error, channel) { if (error) throw error; channelIds.push(channel._id); numChannelsCreated++; if (numChannelsCreated == channelNamesArray.length) { done(); } }) }) }) // remove the 3 channels created before after(function (done) { var numChannelsRemoved = 0; channelNamesArray.forEach(function (channelName) { channelsDB.getChannelByName(channelName, function (error, channel) { if (error) throw error; channel.remove(function () { numChannelsRemoved++; if (numChannelsRemoved == channelNamesArray.length) { done(); } }) }) }) }) it('Should get all channels without error', function (done) { channelsDB.getChannels(function (error, channels) { assert.isAtLeast(channels.length, 3); done(); }) }) it('Should get one channel by id without error', function (done) { channelsDB.getChannel(channelIds[0], function (error, channel) { assert.isNotNull(channel); done(); }) }) }) })

Si ejecutamos mocha en este proyecto, este es el resultado obtenido:

Como veis es rápido y fácil de usar, y en muy poco tiempo podemos crear los unit tests que probarán la funcionalidad que vamos a desarrollar.

Como integrar MongoDB en NodeJS

Artículos relacionados

Encriptación de password en NodeJS y MongoDB: bcrypt

Refresh token con autenticación JWT en NodeJS

OAuth2, protocolo de autorización

Controla el estilo del código de tu equipo: ESLint para JavaScript