Introducción
Contenido
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?
Te puede interesar:Uso de stubs para pruebas en JavaScript con Sinon.jsConsideremos 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.
Te puede interesar:Uso de Mocks para pruebas en JavaScript con Sinon.jsUsando 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.
Te puede interesar:Usando Sequelize ORM con Node.js y ExpressEspí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:
Te puede interesar:Implementación de aplicaciones Node.js en AWS EC2 con Docker$ 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:
Te puede interesar:Clasificación de burbujas y clasificación de coctelera en JavaScript$ 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í.