Testeando JavaScript con Mocha y Chai

No estamos descubriendo nada nuevo si decimos que las pruebas unitarias son importantísimas. Yo me atrevería a decir que son necesarias para asegurar las calidad de nuestro producto.

Es común en muchas empresas centrarse en otro tipos de pruebas, como las end-to-end cuando tienen un desarrollado en Javascript. Bajo mi punto de vista esto es un error común y desde Paradigma queremos proponer una vía para remediarlo.

Mocha y Chai nos permiten crear pruebas unitarias muy completas para nuestro desarrollo en JavaScript. Estas pruebas nos ayudarán a darle un plus de calidad al proyecto y con ello también acercarnos a cumplir con la conocida pirámide de Cohn.

En este artículo vamos a crear un proyecto desde cero en el cual utilizaremos: npm, mocha.js y chai.js. El objetivo final del mismo es conocer las utilidades más importantes que nos ofrecen Mocha y Chai, para poder aplicarlas a nuestro proyecto desarrollado.

NPM (node package manager)

Es el gestor de paquetes Javascript de Node.js más utilizado. Nos proporciona casi cualquier librería disponible simplemente ejecutando un comando. Esto hace posible que podamos trabajar con una nueva librería en cuestión de segundos.

MOCHA.JS

Mocha es un framework de pruebas de JavaScript que se ejecuta en Node.js. Nos da la posibilidad de crear tanto tests síncronos como asíncronos de una forma muy sencilla. Nos proporciona muchas utilidades para la ejecución y el reporte de los tests.

CHAI.JS

Es un librería de aserciones, la cual se puede emparejar con cualquier marco de pruebas de Javascript. Chai tiene varias interfaces: assert, expect y should, que permiten al desarrollador elegir el estilo que le resulte más legible y cómodo a la hora de desarrollar sus tests:

Assert: 
assert.typeOf( foo, 'string', 'foo is a string' );
assert.equal( foo, 'bar', 'foo equal `bar`' );
assert.lengthOf( foo, 3, 'foo`s value has a length of 3' );

Expect: 
expect( foo ).to.be.a( 'string' );
expect( foo ).to.equal( 'bar' );
expect( foo ).to.have.length( 3 );



Should: 
foo.should.be.a( 'string' );
foo.should.equal( 'bar' );
foo.should.have.length( 3 );

Como podemos ver en los ejemplos cada una tiene una estructura diferente, lo cual no nos impide obtener los mismos resultados independientemente de cual utilicemos.

Comenzando el proyecto

Una vez presentados lo actores con los que vamos a trabajar, podemos entrar en la parte más técnica y comenzar con nuestro proyecto.

En primer lugar, instalamos npm para poder gestionar de una forma más sencilla las librerías que necesitemos:

sudo apt-get install npm

Creamos el directorio de nuestro proyecto:

mkdir tests_mocha_chai

Creamos el fichero package.json. Este fichero es utilizado por npm para, entre otras cosas, almacenar la información referente a nuestro proyecto, como pueden ser las dependencias.

cd tests_mocha_chai
npm init

A continuación instalaremos la librería de mocha.js a través de npm. Le añadimos el parámetro –save al comando para que guarde dicha librería en el fichero package.json.

npm install mocha --save

Por el momento tenemos todo lo que necesitamos, así que vamos a ponernos el mono de trabajo y empezar con nuestro código.

En primer lugar vamos a crear una función, para la cual definiremos test unitarios en pasos siguientes. Para ello, creamos el fichero calculator.js dentro de una carpeta que llamaremos “app” que crearemos en la raíz de nuestro proyecto:

exports.addTested = function(value) {
	
  var result = value + " tested";
  return result;

};

Con este código tendremos una función bastante sencilla que simplemente recibe un parámetro y lo devuelve añadiendo la cadena “tested” al final del mismo.

Ya tenemos el código a testear, así que vamos a mi parte favorita, que son los tests.
Lo primero que haremos será instalar la dependencia de chai.js de la misma forma que instalamos en pasos anteriores mocha.js:

npm install chai --save

Anteriormente hablamos de tres opciones a la hora de desarrollar tests con Chai: assert, expect, should. Para no centrarnos en ninguna de ellas, vamos a ver como testar la función “addTested” con cada uno de ellas.

Para ello vamos a crear la carpeta “test” en la raíz de nuestro proyecto y dentro de ella un fichero por cada una de ellas:

calculator_chai_assert.js

var assert    = require("chai").assert;
var calculator = require("../app/calculator");

describe("Calcultator tests using ASSERT interface from CHAI module: ", function() {
	describe("Check addTested Function: ", function() {
		it("Check the returned value using: assert.equal(value,'value'): ", function() {
			result   = calculator.addTested("text");
			assert.equal(result, "text tested");
		});		
		it("Check the returned value using: assert.typeOf(value,'value'): ", function() {
			result   = calculator.addTested("text");
			assert.typeOf(result, "string");
		});		
		it("Check the returned value using: assert.lengthOf(value,'value'): ", function() {
			result   = calculator.addTested("text");
			assert.lengthOf(result, 11);
		});				
	});		
	
	
	
	
});

calculator_chai_expect.js

var expect    = require("chai").expect;
var calculator = require("../app/calculator");

describe("Calcultator tests using EXPECT interface from CHAI module: ", function() {
	describe("Check addTested Function: ", function() {
		it("Check the returned value using: expect(value).to.equal('value'): ", function() {
			result   = calculator.addTested("text");
			expect(result).to.equal("text tested");
		});		
		it("Check the returned value using: expect(value).to.be.a('value')): ", function() {
			result   = calculator.addTested("text");
			expect(result).to.be.a('string');
		});		
		it("Check the returned value using: expect(value).to.have.lengthOf(value): ", function() {
			result   = calculator.addTested("text");
			expect(result).to.have.lengthOf(11);
		});		
	});
});

Calculator_chai_should.js

var should    = require("chai").should();
var calculator = require("../app/calculator");

describe("Calcultator tests using SHOULD interface from CHAI module: ", function() {
	describe("Check addTested Function: ", function() {
		it("Check the returned value using: value.should.equal(value): ", function() {
			result   = calculator.addTested("text");
			result.should.equal("text tested");
		});		
		it("Check the returned value using: value.should.be.a('value'): ", function() {
			result   = calculator.addTested("text");
			result.should.be.a('string');
		});		
		it("Check the returned value using: expect(value).to.have.lengthOf(value): ", function() {
			result   = calculator.addTested("text");
			result.should.have.lengthOf(11);
		});		
	});

});

Desmontando los tests

Por fin tenemos nuestros ficheros de tests creados. Ahora vamos a hablar de su estructura:

Cabecera:

En la parte superior del fichero podemos ver algo similar a:

var assert    = require("chai").assert;
var calculator = require("../app/calculator");

Esto no son más que las importaciones necesarias para trabajar desde nuestro fichero de pruebas. La primera de ellas es para importar la interfaz de Chai con la que trabajaremos y la segunda para importar el fichero calculator, el cual incluye la función que vamos a probar.

Describe:

Utilizamos este elemento para definir bloques de pruebas relacionadas entre sí. Podemos concatenar varios “describe” si lo vemos necesario para nuestra estructura de tests.

It:

Cada elemento it sería una prueba. Podemos definir todas las pruebas (it) que sean necesarias dentro de un elemento describe. Las pruebas pueden estar divididas entre varios elementos de tipo “describe”. Esto es útil si queremos diferenciar varios bloques de pruebas en un mismo fichero.

Esta sería la estructura más sencilla de un caso de prueba utilizando Mocha.js. No pensemos que esto es todo. Esta librería nos das muchas más posibilidades. Por ello, hablaremos de las que me resultan más interesantes al final de este post.

Ejecutando los tests

Seguimos avanzando en nuestro proyecto. Ya tenemos tanto una función, como varias pruebas que comprobarán que funciona a la perfección.
Ya que estamos en un proyecto con npm, utilizaremos las posibilidades que nos ofrece para ejecutar nuestras pruebas. Para ello, editaremos el fichero package.json y le daremos valor al parámetro “test” que por defecto lo encontraremos vacío:

"test": "./node_modules/.bin/mocha --reporter spec"

Después de actualizar el fichero package.json podemos lanzar los tests con el comando:

npm test

Tras ejecutar el comando debemos obtener una salida en la cual se muestra el resultado de cada uno de los tests ejecutados y la duración completa de los mismos.

Ampliando conocimientos de Mocha.js

Hasta el momento hemos visto la estructura de los tests formada que describe it, pero Mocha nos ofrece bastantes opciones más.

Hooks:

Se colocan dentro de un elemento de tipo describe y son muy útiles para algo tan simple y necesario como para inicializar una variable, limpiar la base de datos…

before(function() {
    // Se ejecuta antes de todas las pruebas del bloque
  });

  after(function() {
    // Se ejecuta después de todas las pruebas del bloque
  });

  beforeEach(function() {
    // Se ejecuta antes de cada prueba del bloque
  });

  afterEach(function() {
    // Se ejecuta después de cada prueba del bloque
  });

Exclusive Tests:

La función only() hace posible ejecutar solo el bloque o el caso de prueba al cual se lo añadamos. Es muy útil si tenemos un gran número de tests y por algún motivo queremos ejecutar un parte de ellos o incluso un único test.

    • Caso 1
describe.only('bloque con llamada a función only', function() {
    it.('test1, function() {
      // ...
    });
   it.('test2, function() {
      // ...
    });
 });

En este caso si ejecutamos los tests. Se ejecutarán los tests 1 y 2.

    • Caso 2
describe('bloque que incluye un test con llamada a función only', function() {
    it.only('test1, function() {
      // …
    it.('test2, function() {
      // …
    });
 });

En este otro caso si ejecutamos los tests solo se ejecutará el test 1.

Inclusive Tests:

Podemos decir que la función skip() se comporta de forma opuesta a only(). Con skip podemos provocar que, durante una ejecución, no se ejecute un bloque o incluso un test dentro del mismo. Es muy útil si en algún momento tenemos un bloque de tests o un solo test que no queremos que se ejecute por algún motivo.

    • Caso 1
describe.skip('bloque con llamada a función only', function() {
    it.('test1, function() {
      // ...
    });
   it.('test2, function() {
      // ...
    });
 });

En este caso no se ejecutaría ningún test.

    • Caso 2
describe('bloque que incluye un test con llamada a función only', function() {
    it.skip('test1, function() {
      // …
    it.('test2, function() {
      // …
    });
 });

En este solo se ejecutará el test 2.

Espero haberos mostrado un nuevo camino. Un camino que os ayudará sin ninguna duda a mejorar la calidad de vuestros proyectos Javascript. Si no tenéis un proyecto donde implantarlo en estos momentos, espero al menos que os haya gustado y que os entre el gusanillo para seguir investigando sobre el tema.

Os dejo el código que hemos utilizado en los ejemplos más algún añadido en mi repositorio.

“Hay dos formas de escribir programas sin errores; sólo la tercera funciona” – Alan J. Perlis

Quality Assurance con más de 7 años de experiencia. Enamorado de las nuevas tecnologías y de todo lo relacionado con la calidad de software. Apasionado del deporte, con el “Manque Pierda” como filosofía de vida.

Ver toda la actividad de Rafael Márquez

2 comentarios

  1. Carlos dice:

    Muchas gracias por la información! Justo estabamos pensando en montarnos esta infraestructura para nuestras pruebas.

    Facil, conciso y se entiende perfectamente.

    Muchaa gracias de nuevo

  2. Jose Antonio dice:

    Muchas gracias por el post Rafa. Siempre los primeros pasos son los más complicados y vienen perfectamente detallados. Lo aplicaremos en cuanto lo requiera alguno de los proyectos.

    Un saludo.

Escribe un comentario