A la hora de realizar una prueba unitaria en Javascript, la mayoría de los programadores nos preguntamos: ¿Qué debería cubrir mi prueba? ¿Qué framework debería usar?¿Estoy realizando el test correctamente?

En este post intentaré responder a cada una de esas preguntas para facilitaros el trabajo a la hora de escribir una prueba unitaria.

¿Qué es un framework de pruebas unitarias?

Un framework de pruebas unitarias es una herramienta que nos permite escribir pruebas sobre un bloque de código, ejecutándolo bajo un entorno de javascript sin necesidad de interferir en el IDE ni en la propia aplicación.

¿Para qué sirven los test unitarios?

Los test unitarios sirven para asegurarnos de que un bloque de nuestro código, función o clase, funcionan correctamente y abarca la mayoría de casos de uso que se puedan dar. Además, proporciona robustez y calidad a nuestro código y confirma que funciona correctamente.

¿Qué debería cubrir mi prueba unitaria?

Nuestra prueba unitaria deberá cubrir todas las posibles funcionalidades dentro de un bloque de código. Por ejemplo, dado el siguiente código, vamos a analizar qué casos de uso nos hacen falta:

const objectMapping = {
    ACTIVE: 'Activo',
    INACTIVE: 'Inactivo',
    OBSOLETE: 'Obsoleto',
};

function getObjectDescription(type) {
    if (!type) {
        return "El argumento 'type' no existe";
    }
    return objectMapping[type];
}

export default getObjectDescription;

Tenemos un módulo que nos exporta el método “getObjectDescription” al que pasándole como parámetro el “type” nos devuelve un “string”. Ahora bien, identifiquemos los diferentes casos que se nos pueden dar con dicho módulo:

  1. Qué “getObjectDescription” exista.
  2. Llamar al método “getObjectDescription” con un parámetro que exista en el “objectMapping”.
  3. Llamar al método “getObjectDescription” con un parámetro que no exista en el “objectMapping”.
  4. Llamar al método “getObjectDescription” con un parámetro vacío.
  5. Llamar al método “getObjectDescription” sin parámetro.

Estos son, a primera vista, los casos de uso que se pueden presentar en dicho bloque de código.

¿Qué framework de pruebas unitarias debería usar?

Actualmente, disponemos de muchos frameworks para realizar las pruebas de nuestro código en Javascript, estos son los más populares del año pasado:

Jest

Mocha

Jasmine

Aunque podríamos analizar uno a uno dichos frameworks, como el objetivo del post es aprender a escribir bien nuestros test, optaremos por Jest y veremos cómo escribir correctamente un test en dicho framework.

¿Estoy realizando el test correctamente?

Antes de empezar a escribir nuestro test hay ciertas cosas que deberíamos conocer o, por lo menos, que nos resulten familiares:

describe(‘[group description], () => …);

beforeEach(() => …);

afterEach(() => …);

beforeAll(() => …);

afterAll(() => …);

it(‘[test description]’, () => …);

expect()...

Empezamos con nuestra prueba (recordad que debemos cubrir los puntos descritos anteriormente). Pero, antes, deberemos preparar nuestro fichero de test. Por lo general, los ficheros de test se suelen ubicar en la misma carpeta donde se encuentra el fichero que vamos a usar para las pruebas, poniéndole el mismo nombre seguido de .spec.js o .test.js. Personalmente, prefiero tener separado los ficheros de pruebas de los de la aplicación. Por ello, suelo crearme una carpeta con los test a la misma altura de donde se encuentran las fuentes, siguiendo una estructura similar a la contenida en src.

Volvamos a la generación de nuestro fichero de test. Todo fichero de test tiene la siguiente estructura base:

import getObjectDescription from [ruta de nuestro fichero];
describe('Test unitarios de nuestro módulo "getObjectDescription"', () => {
    // En el caso de querer reiniciar o volver a instanciar nuestro módulo usaremos los métodos “beforeEach”, “afterEach”, “beforeAll” o “afterAll” según nos convenga.
});

Una vez que tengamos nuestra estructura de fichero, vamos con los test:

  1. Que “getObjectDescription” exista.
it('getObjectDescription to be truthy, () => {
// Con toBeTruthy estamos diciendole a jest que esperamos que exista nuestro método
    expect(getObjectDescription).toBeTruthy();
});
  1. Llamar al método “getObjectDescription” con un parámetro que exista en el “objectMapping”. Dicho método nos devuelve un string que nos sirve para comprobar que lo que nos devuelve, es algo que existe: Deberemos almacenarlo en una variable y comprobar dicha variable.
it("getObjectDescription('ACTIVE') to be 'Activo'", () => {
    const result = getObjectDescription('ACTIVE');
expect(result).toBe('Activo'); // Ponemos 'Activo' porque es el valor de nuestro objeto
}); 
  1. Llamar al método “getObjectDescription” con un parámetro que no exista en el “objectMapping”.
it("getObjectDescription('DRAFT') to be false", () => {
    const result = getObjectDescription('DRAFT');
expect(result).toBeFalsy(); // toBeFalsy comprueba si el valor es nulo/undefined/false
});
  1. Llamar al método “getObjectDescription” con un parámetro vacío. Exactamente igual al test anterior.
it("getObjectDescription('') to be false", () => {
    const result = getObjectDescription(');
expect(result).toBeFalsy(); // toBeFalsy comprueba si el valor es nulo/undefined/false
}); 
  1. Llamar al método “getObjectDescription” sin parámetro.
it("getObjectDescription() to be 'El argumento 'type' no existe'", () => {
    const result = getObjectDescription(');
expect(result).toBe('El argumento 'type' no existe');
}); 

Y nuestro test debería quedar algo parecido a lo siguiente:

import getObjectDescription from [ruta de nuestro fichero];

describe('Test unitarios de nuestro módulo "getObjectDescription"', () => {
// En el caso de querer reiniciar o volver a instanciar nuestro módulo usaremos los métodos "beforeEach", "afterEach", "beforeAll" o "afterAll" según nos convenga.

    it('getObjectDescription to be truthy, () => {
// Con toBeTruthy estamos diciendole a jest que esperamos que exista nuestro método
    expect(getObjectDescription).toBeTruthy();
});

it("getObjectDescription('ACTIVE') to be 'Activo'", () => {
    const result = getObjectDescription('ACTIVE');
expect(result).toBe('Activo'); // Ponemos 'Activo' porque es el valor de nuestro objeto
});

it("getObjectDescription('DRAFT') to be false", () => {
    const result = getObjectDescription('DRAFT');
expect(result).toBeFalsy(); // toBeFalsy comprueba si el valor es nulo/undefined/false
});

it("getObjectDescription('') to be false", () => {
    const result = getObjectDescription(');
expect(result).toBeFalsy(); // toBeFalsy comprueba si el valor es nulo/undefined/false
});

it("getObjectDescription() to be 'El argumento 'type' no existe'", () => {
    const result = getObjectDescription(');
expect(result).toBe('El argumento 'type' no existe');
}); 

});

Conclusiones

Escribir un test unitario no es algo complejo, pero sí que lleva su tiempo. Normalmente, si en realizar una funcionalidad se tarda 1 hora, para realizar los test de dicha funcionalidad se suele calcular multiplicando por tres el tiempo de desarrollo, ya que tenemos que abarcar, a ser posible, un mínimo del 90% de los posibles casos que se puedan dar: por ejemplo, cuando el código se ejecuta correctamente o cuando queremos controlar una excepción.
Todo ello conlleva unas acciones que, para tener un test correcto, hay que cubrir en la mayoría de posibilidades o casos que se puedan dar en nuestro código.

Cuéntanos qué te parece.

Enviar.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.