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

    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铆.

     

    Etiquetas:

    Deja una respuesta

    Tu direcci贸n de correo electr贸nico no ser谩 publicada. Los campos obligatorios est谩n marcados con *