Uso de simulacros para realizar pruebas en JavaScript con Sinon.js

Sinon.js es un marco popular que se utiliza en espías de prueba independientes, stubs y simulacros para JavaScript. En este artículo, simularemos una solicitud HTTP en una prueba unitaria.

Introducción

Los "simulacros" de prueba son objetos que reemplazan objetos reales mientras simulan sus funciones. Un simulacro también tiene expectativas sobre cómo se utilizarán las funciones que se están probando.

En algunos casos de prueba unitaria, es posible que queramos combinar la funcionalidad de espías, para observar el comportamiento de un método bajo llamada, y la de stubs, para reemplazar la funcionalidad de un método, para asegurarnos de que no hacemos una función real. llamar pero aún podemos monitorear el comportamiento de nuestra función de destino en consecuencia. En tal caso, podemos usar simulacros.

En este artículo, buscaremos comprender qué son los simulacros y cómo usarlos en las pruebas unitarias. Luego obtendremos experiencia práctica con Sinon.js para simular una solicitud HTTP.

Este artículo es el tercero de nuestra serie sobre técnicas de pruebas unitarias con Sinon.js. Le recomendamos que lea también nuestros artículos anteriores sobre este tema:

¿Qué son los simulacros? {#lo que se burla}

Los simulacros combinan la funcionalidad de spies y stubs, lo que significa que reemplazan la función de destino pero al mismo tiempo nos brindan la capacidad de observar cómo se llamó a la función.

Además, los simulacros tienen afirmaciones integradas llamadas expectativas. Usted define las expectativas de cómo se usará su función simulada por adelantado. Si su simulacro no satisfizo sus expectativas, su prueba fallará.

Por ejemplo, consideremos una función que se comunica con una base de datos para guardar los detalles de un contacto. Con una base de datos simulada, en lugar de una base de datos real, nuestra función encontrará un objeto de base de datos falso. Podemos determinar qué tipo de respuesta dará. También indicaremos cuántas veces se debe llamar a la base de datos y los argumentos con los que se debe llamar.

Finalmente, como parte de la prueba, verificamos que nuestro simulacro de base de datos haya sido llamado la cantidad exacta de veces que esperábamos. También verificamos que se haya llamado solo con los argumentos que nuestra función supuestamente debe proporcionar.

Habiendo visto lo que son los simulacros, veamos ahora las situaciones en las que podemos emplearlos.

¿Por qué usar simulacros? {#por qué se burla}

Los simulacros son útiles cuando se valida cómo se usa una dependencia externa dentro de una función. Use simulacros cuando esté interesado en:

  • Confirmar que su dependencia externa se utiliza en absoluto
  • Verificando que tu dependencia externa sea utilizada correctamente
  • Asegurarse de que su función pueda manejar diferentes respuestas de dependencias externas.

Imagine que está probando una función que habla con una API de terceros para obtener algunos datos del usuario. Para realizar solicitudes a la API externa, primero deberá realizar algunas llamadas para autenticarse. Ya se está volviendo un inconveniente usar la API real en las pruebas. Además, es posible que no siempre tenga una conexión a Internet para acceder a la API mientras ejecuta sus pruebas.

Con un simulacro, devolveremos respuestas falsas. Ahora podemos probar rápidamente que nuestra función se comporta correctamente cuando se le dan los datos falsos en un formato particular. También sabremos que nuestra función realizó solicitudes a la API con los parámetros correctos.

Veamos ahora cómo podemos usar Sinon.js para crear simulacros.

Usando Sinon.js para crear un simulacro

Usaremos Sinon.js para simular una respuesta de una API JSON que recupera una lista de fotos en un álbum. Además de Sinon.js, usaremos Moca y Chai para configurar y ejecutar las pruebas. Puedes leer nuestra guía Nuestra guía para saber más sobre ellos antes de continuar.

Configuración

Cree un directorio llamado SinonMock y acceda a él:

1
2
$ mkdir SinonMock
$ cd SinonMock

Luego usaremos NPM para inicializar un proyecto para rastrear los archivos de proyecto que creamos:

1
$ npm init -y

A continuación, instalaremos Mocha y Chai como dependencias de prueba para ejecutar nuestras pruebas, junto con Sinon.js:

1
$ npm i mocha chai sinon --save-dev

Habiendo completado nuestra configuración, simulemos una solicitud HTTP.

Simulando una llamada HTTP con Sinon.js

In our artículo anterior sobre espías de prueba, we spied on an HTTP request to the photo album API. We'll continue with that example for this article.

Cree un archivo en la raíz del directorio SinonMock y llámelo index.js:

1
$ touch index.js

En el archivo creado, ingrese el siguiente código:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
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, getAlbumById() es una función que llama a una API JSON que devuelve una lista de fotos. Proporcionamos un ID de álbum como argumento de la función. Anteriormente exploramos la creación de apéndices y el espionaje en el método request.get().

Ahora, imitaremos el objeto request y comprobaremos si el método get() se llama una vez, según sea necesario, y verificaremos si recibió los argumentos correctos. Luego verificaremos que nuestra función tenga las propiedades correctas en función de lo que devolvió nuestro simulacro.

Cree otro archivo en la raíz del directorio SinonMock y llámelo index.test.js:

1
$ touch index.test.js

Abra el archivo index.test.js con un editor e ingrese el siguiente código:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
const expect = require('chai').expect;
const request = require('request');
const sinon = require('sinon');
const index = require('./index');

describe('with mock: getPhotosByAlbumId', () => {
    it('should getPhotosByAlbumId', (done) => {
        let requestMock = sinon.mock(request);
        const myPhotos = [{
            "albumId": 1,
            "id": 1,
            "title": "accusamus beatae ad facilis cum similique qui sunt",
            "url": "https://via.placeholder.com/600/92c952",
            "thumbnailUrl": "https://via.placeholder.com/150/92c952"
        },
        {
            "albumId": 1,
            "id": 2,
            "title": "reprehenderit est deserunt velit ipsam",
            "url": "https://via.placeholder.com/600/771796",
            "thumbnailUrl": "https://via.placeholder.com/150/771796"
        },
        {
            "albumId": 1,
            "id": 3,
            "title": "officia porro iure quia iusto qui ipsa ut modi",
            "url": "https://via.placeholder.com/600/24f355",
            "thumbnailUrl": "https://via.placeholder.com/150/24f355"
        }];

        requestMock.expects("get")
            .once()
            .withArgs('https://jsonplaceholder.typicode.com/albums/2/photos?_limit=3')
            .yields(null, null, JSON.stringify(myPhotos));

        index.getAlbumById(2).then((photos) => {
            expect(photos.length).to.equal(3);
            photos.forEach((photo) => {
                expect(photo).to.have.property('id');
                expect(photo).to.have.property('title');
                expect(photo).to.have.property('url');
            });

            requestMock.verify();
            requestMock.restore();
            done();
        });
    });
});

En nuestro caso de prueba anterior, primero creamos un simulacro del objeto request usando sinon.mock() y lo llamamos requestMock. El objeto requestMock tiene las funciones del objeto request pero las funciones no hacen nada por defecto.

Después de proporcionar algunos datos de fotos fijos, anulamos el método get() original del objeto de solicitud mediante el uso del método expect() de la API simulada de Sinon.js.

El método expect() toma un solo argumento, que es el método del objeto simulado que anticipamos que se usará.

El método once() afirma que nuestra expectativa se llama una vez. En este caso, el método get() del objeto de solicitud se llamará exactamente una vez.

El método withArgs() afirma que esperamos que se llame al método get() con la matriz de argumentos que le proporcionamos. En nuestro caso la URL de la API.

El método yields() coloca datos en la devolución de llamada que acepta nuestro objeto simulado. En este caso, nuestro error y nuestra respuesta son null pero nuestro cuerpo tiene una respuesta JSON.

Después de esta configuración, llamamos a nuestro método getAlbumById() y verificamos si las fotos volvieron a tener las propiedades correctas.

Observe la llamada verify() del objeto requestMock para confirmar que se cumplieron nuestras expectativas. Si las expectativas fallan, la prueba generará una excepción. Luego llamamos al método restore() para descartar el simulacro creado por nuestra prueba y restaurar el objeto de solicitud original.

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

1
2
3
4
5
6
7
8
9
$ mocha index.test.js

with mock: getPhotosByAlbumId
     should getPhotosByAlbumId


  1 passing (13ms)

  Done in 0.72s.

Para confirmar el comportamiento de nuestro simulacro, veamos si las expectativas fallan si cambiamos la URL con la que nos comunicamos. En su archivo index.js, cambie:

1
const requestUrl = `https://jsonplaceholder.typicode.com/albums/${id}/photos?_limit=3`;

A:

1
const requestUrl = `https://example.com`;

Ahora ejecute las pruebas una vez más:

1
$ mocha index.test.js

Dado que cambiamos la entrada al método get(), el argumento de la URL ya no coincide con lo que está en nuestra prueba. Obtendremos este resultado cuando ejecutemos la prueba:

1
2
3
4
5
6
7
> mocha index.test.js



  with mock: getPhotosByAlbumId
(node:85434) UnhandledPromiseRejectionWarning: ExpectationError: Unexpected call: get(https://example.com, function () {})
    Expected get(https://jsonplaceholder.typicode.com/albums/2/photos?_limit=3[, ...]) once (never called)

¡Excelente! ¡Estamos bastante seguros de que nuestras simulaciones garantizarán que nuestra función se comporte como esperamos!

Mediante el uso de un simulacro, hemos podido obtener los beneficios tanto de los espías como de los stubs. Pudimos verificar que nuestra función fue llamada exactamente una vez y con los argumentos correctos, un beneficio que nos proporcionaron los espías. También pudimos bloquear la solicitud para que no hiciéramos una llamada HTTP real a la API, lo que aseguró que nuestra prueba se ejecutara rápidamente.

Conclusión

Los simulacros en las pruebas unitarias combinan la funcionalidad tanto de los espías como de los stubs reemplazando funciones como stubs y al mismo tiempo brindándonos un medio de observar las funciones para verificar cómo se llamaron, la funcionalidad que nos brindan los espías. Los simulacros nos dan el beneficio de verificar cómo se usó nuestra función en una prueba.

En este artículo, presentamos el concepto de simulacro en las pruebas unitarias y vimos cómo podíamos simular una llamada HTTP. Para obtener más información sobre los simulacros de Sinon.js, puede consultar la documentación oficial de la API de simulacros. os.