Uso de espías para realizar pruebas en JavaScript con Sinon.js

U

Introducción

En las pruebas de software, un “espía” registra cómo se utiliza una función cuando se prueba. Esto incluye cuántas veces se llamó, si se llamó con los argumentos correctos y qué se devolvió.

Si bien las pruebas se utilizan principalmente para validar la salida de una función, a veces necesitamos validar cómo una función interactúa con otras partes del código.

En este artículo, veremos en profundidad qué son los espías y cuándo deben usarse. Luego, espiaremos una solicitud HTTP mientras usamos Sinon.js en una prueba unitaria de JavaScript.

Este artículo es el segundo de una serie sobre técnicas de prueba con Sinon.js. Le recomendamos que lea también nuestro artículo anterior:

  • Uso de stubs para pruebas en JavaScript con Sinon.js
  • Uso de espías para realizar pruebas en JavaScript con Sinon.js (está aquí)
  • Uso de Mocks para pruebas en JavaScript con Sinon.js

¿Qué son los espías?

Un espía es un objeto en pruebas que rastrea las llamadas realizadas a un método. Al rastrear sus llamadas, podemos verificar que se está usando de la forma en que se espera que lo use nuestra función.

Fiel a su nombre, un espía nos da detalles sobre cómo se usa una función. ¿Cuántas veces se llamó? ¿Qué argumentos se pasaron a la función?

Consideremos una función que verifica si existe un usuario y crea uno en nuestra base de datos si no existe. Podemos eliminar las respuestas de la base de datos y obtener los datos correctos del usuario en nuestra prueba. Pero, ¿cómo sabemos que la función realmente está creando un usuario en los casos en que no tenemos datos de usuario preexistentes? Con un espía, observaremos cuántas veces se llama a la función crear usuario y estaremos seguros.

Ahora que sabemos qué es un espía, pensemos en las situaciones en las que deberíamos usarlos.

¿Por qué utilizar espías?

Los espías se destacan por dar una idea del comportamiento de la función que estamos probando. Si bien validar las entradas y salidas de una prueba es crucial, examinar cómo se comporta la función puede ser crucial en muchos escenarios:

Cuando su función tiene efectos secundarios que no se reflejan en sus resultados, debe espiar los métodos que utiliza.

Un ejemplo sería una función que devuelve JSON a un usuario después de realizar muchas llamadas a varias API externas. La carga útil final de JSON no le dice al usuario cómo la función recupera todos sus datos. Un espía que monitorea cuántas veces llamó a las API externas y qué entradas utilizó en esas llamadas nos diría cómo.

Veamos cómo podemos usar Sinon.js para crear espías en nuestro código.

Usando Sinon.Js para crear un espía

Hay varias formas de crear un espía con Sinon.js, cada una con sus ventajas y desventajas. Este tutorial se centrará en los dos métodos siguientes, que apuntan a los espías en una sola función a la vez:

  • Una función anónima que rastrea argumentos, valores y llamadas realizadas a un método.
  • Un contenedor para una función existente.

Primero, configuremos nuestro proyecto para que podamos ejecutar nuestros archivos de prueba y usar Sinon.js.

Preparar

Comencemos creando una carpeta para almacenar nuestro código JavaScript. Crea una nueva carpeta y muévete a ella:

$ mkdir SpyTests
$ cd SpyTests

Inicialice NPM para que pueda realizar un seguimiento de los paquetes que instala:

$ npm init -y

Ahora instalemos nuestras dependencias de prueba. Instalamos Moca y Chai para ejecutar nuestras pruebas, junto con Sinon.js:

$ npm i mocha chai sinon --save-dev

¡Nuestra configuración está completa! Comencemos por usar espías como funciones anónimas.

Espías con funciones anónimas

Como funciones anónimas, los espías de Sinon.js a menudo son útiles en los casos en que queremos probar funciones de orden superior que toman otras funciones, es decir, devoluciones de llamada como argumentos. Veamos un ejemplo básico que vuelve a implementar el Array.prototype.map() con una devolución de llamada:

Crea dos archivos, es decir mapOperations.js y mapOperations.test.js dentro de spyTests directorio de la siguiente manera:

$ touch mapOperations.js mapOperations.test.js

Ingrese el siguiente código en el mapOperations.js archivo:

const map = (array, operation) => {
    let arrayOfMappedItems = [];
    for (let item of array) {
        arrayOfMappedItems.push(operation(item));
    }
    return arrayOfMappedItems;
};

console.log(map([{ name: 'john', role: 'author'}, { name: 'jane', role: 'owner'}], user => user.name));

module.exports = { map };

En el código de arriba, map() toma una matriz como primer argumento y una función de devolución de llamada, operation(), que transforma los elementos de la matriz como segundo argumento.

Dentro de map() función, iteramos a través de la matriz y aplicamos la operación en cada elemento de la matriz, luego empujamos el resultado al arrayOfMappedItems formación.

Cuando ejecuta este ejemplo en la consola, debería obtener el siguiente resultado:

$ node mapOperations.js
[ 'john', 'jane' ]

Para probar si el operation() la función fue llamada por nuestro map() función, podemos crear y pasar un espía anónimo al map() funcionan de la siguiente manera:

const { map } = require('./mapOperations');
const sinon = require('sinon');
const expect = require('chai').expect;

describe('test map', () => {
    const operation = sinon.spy();

    it('calls operation', () => {
        map([{ name: 'foo', role: 'author'}, { name: 'bar', role: 'owner'}], operation);
        expect(operation.called);
    });
});

Si bien nuestra devolución de llamada no transforma realmente la matriz, nuestro espía puede verificar que la función que estamos probando realmente la use. Esto se confirma cuando expect(operation.called); no falla la prueba.

¡Veamos si nuestra prueba pasa! Ejecute la prueba, debería obtener el siguiente resultado:

$ mocha mapOperations.test.js

  test map

    ✓ calls operation


  1 passing (4ms)

✨  Done in 0.58s.

¡Funciona! Ahora estamos seguros de que nuestra función utilizará cualquier devolución de llamada que pongamos en sus argumentos. Veamos ahora cómo podemos ajustar una función o método usando un espía.

Espías como envoltorios de funciones o métodos

En el artículo anterior, vimos cómo podemos apuntar una solicitud HTTP en nuestras pruebas unitarias. Usaremos el mismo código para mostrar cómo podemos usar Sinon.js para espiar una solicitud HTTP.

En un nuevo archivo llamado index.js, agregue el siguiente código:

const request = require('request');

module.exports = {
    getAlbumById: async function(id) {
        const requestUrl = `https://jsonplaceholder.typicode.com/albums/${id}/photos?_limit=3`;
        return new Promise((resolve, reject) => {
            request.get(requestUrl, (err, res, body) => {
                if (err) {
                    return reject(err);
                }
                resolve(JSON.parse(body));
            });
        });
    }
};

En resumen, el getAlbumById() El método llama a una API JSON que obtiene una lista de fotos de un álbum cuya ID pasamos como parámetro. Anteriormente, cortamos el request.get() método para devolver una lista fija de fotos.

Esta vez, espiaremos el request.get() método para que podamos verificar que nuestra función realiza una solicitud HTTP a la API. También comprobaremos que haya realizado la solicitud una vez, lo cual es bueno ya que no queremos un error que envíe spam al punto final de la API.

En un nuevo archivo de prueba llamado index.test.js, escriba el siguiente código JavaScript línea por línea:

const expect = require('chai').expect;
const request = require('request');
const sinon = require('sinon');
const index = require('./index');

describe('test getPhotosByAlbumId', () => {
    let requestSpy;
    before(() => {
        requestSpy = sinon.spy(request, 'get');
    });

    after(() => {
        request.get.restore();
    });

    it('should getPhotosByAlbumId', (done) => {
        index.getAlbumById(2).then((photos) => {
            expect(requestSpy.calledOnce);
            expect(requestSpy.args[0][0]).to.equal("https://jsonplaceholder.typicode.com/albums/2/photos?_limit=3");
            photos.forEach(photo => {
                expect(photo).to.have.property('id');
                expect(photo).to.have.property('title');
                expect(photo).to.have.property('url');
            });
            done();
        });
    });
});

En la prueba anterior, envolvimos el request.get() método con un espía durante la instalación en el before() función. Restauramos la función cuando desmontamos la prueba en el after() función.

En el caso de prueba, hicimos la afirmación de que requestSpy, el objeto que rastrea request.get()de uso, solo registra una llamada. Luego profundizamos para confirmar que su primer argumento de la request.get() call es la URL de la API JSON. Luego hicimos afirmaciones para asegurarnos de que las fotos devueltas tengan las propiedades esperadas.

Cuando ejecute la prueba, debería obtener el siguiente resultado:

$ mocha index.test.js


  test getPhotosByAlbumId
    ✓ should getPhotosByAlbumId (570ms)


  1 passing (587ms)

✨  Done in 2.53s.

Tenga en cuenta que esta prueba realizó una solicitud de red real a la API. El espía envuelve la función, ¡no reemplaza su funcionalidad!

Además, los talones de prueba de Sinon.js ya son espías. Si alguna vez crea un código auxiliar de prueba, podrá ver cuántas veces se llamó y los argumentos que se pasaron a la función.

Conclusión

Un espía en las pruebas nos brinda una forma de rastrear las llamadas realizadas a un método para que podamos verificar que funciona como se esperaba. Usamos espías para verificar si un método fue llamado o no, cuántas veces fue llamado, con qué argumentos fue llamado y también el valor que devolvió cuando fue llamado.

En este artículo, presentamos el concepto de espías y vimos cómo podemos usar Sinon.js para crear espías. También vimos cómo podemos crear espías como funciones anónimas y cómo podemos usarlos para ajustar métodos. Para casos de uso más avanzados, Sinon.js proporciona una API de espionaje enriquecida que podemos aprovechar. Para más detalles, se puede acceder a la documentación aquí.

 

About the author

Ramiro de la Vega

Bienvenido a Pharos.sh

Soy Ramiro de la Vega, Estadounidense con raíces Españolas. Empecé a programar hace casi 20 años cuando era muy jovencito.

Espero que en mi web encuentres la inspiración y ayuda que necesitas para adentrarte en el fantástico mundo de la programación y conseguir tus objetivos por difíciles que sean.

Add comment

Sobre mi

Últimos Post

Etiquetas

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con tus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, aceptas el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Más información
Privacidad