1. Code
  2. Coding Fundamentals
  3. Testing

Pruebas de Simulación HTTP en Node.js

Las pruebas de simulación HTTP te permiten implementar y probar una función incluso si los servicios dependientes no están implementados todavía. También, puedes probar los servicios que ya se han implementado sin realmente llamar a estos, en otras palabras puedes probarlos sin conexión. Debes analizar las necesidades de tu negocio para la aplicación primer y escribirlos como escenarios en tus documentos de diseño. Digamos que estás desarrollando un cliente API en NodeJS para un servicio de medios de comunicación (no hay ningún servicio de este tipo en la vida real, solo para pruebas) de música, vídeo, fotos, etcetera. Habrá un montón de características en este API, y puede haber necesidad de algunos cambios de vez en cuando.
Scroll to top

Spanish (Español) translation by James Kolce (you can also view the original English article)

Final product imageFinal product imageFinal product image
What You'll Be Creating

Las pruebas de simulación HTTP te permiten implementar y probar una función incluso si los servicios dependientes no están implementados todavía. También, puedes probar los servicios que ya se han implementado sin realmente llamar a estos, en otras palabras puedes probarlos sin conexión. Debes analizar las necesidades de tu negocio para la aplicación primer y escribirlos como escenarios en tus documentos de diseño.

Digamos que estás desarrollando un cliente API en NodeJS para un servicio de medios de comunicación (no hay ningún servicio de este tipo en la vida real, solo para pruebas) de música, vídeo, fotos, etcetera. Habrá un montón de características en este API, y puede haber necesidad de algunos cambios de vez en cuando.

Si no tienes pruebas para esta API, no sabrás qué problemas puede causar. Sin embargo, si tienes pruebas para esta API, puedes detectar problemas mediante la ejecución de todas las pruebas que has escrito. Si estás desarrollando una nueva característica, necesitas agregar casos de prueba especificados para ello. Puedes encontrar una API que ya se han implementado en este repositorio de GitHub. Puedes descargar el proyecto y ejecutar npm test para poder ejecutar todas las pruebas. Vamos a continuar con la parte de la prueba.

Para una prueba de simulación de HTTP, utilizamos nock, que es una biblioteca de simulación y expectativas HTTP y para NodeJS. En las pruebas de simulación de HTTP, puedes aplicar el siguiente flujo:

  1. Definir una regla de simulación de solicitud/respuesta.
  2. Crear una prueba y utilizar su función en la prueba.
  3. Comparar los resultados esperados con los resultados reales en la devolución de llamada de prueba.

Prueba Simple

Pensemos en un cliente API de media. Digamos que llamas la función musicsList mediante el uso de una instancia de la biblioteca Media como a continuación:

1
var Media = require('../lib/media');
2
var mediaClient = new Media("your_token_here");
3
mediaClient.musicsList(function(error, response) {
4
    console.logs(response);    
5
})

En este caso, obtendrá una lista de música en la variable de respuesta mediante una petición a https://ap.example.com/musics dentro de esta función. Si quieres escribir para esto, necesitas simular las peticiones para hacer tus pruebas de funcionamiento offline. Vamos a simular esta petición en nock.

1
describe('Music Tests', function () {
2
    it('should list music', function (done) {
3
        nock('https://api.example.com')
4
            .get('/musics')
5
            .reply(200, 'OK');
6
        mediaClient.musicList(function (error, response) {
7
            expect(response).to.eql('OK')
8
            done()
9
        })
10
    })
11
})

describe('Musics Tests', function()... es para agrupar las pruebas. En el ejemplo anterior, estamos agrupando pruebas relacionadas a música en el mismo grupo. it('should list music', function(done)... es para probar acciones especificas en funciones relacionadas con música. En cada prueba, el callback done es provisto para revisar el resultado de la prueba dentro de la función callback de la función real. En la solicitud simulada, asumimos que se responde OK si llamamos a la función de musicList. Se compara el resultado esperado y real dentro de la función de devolución de llamada.

Igualación de un Archivo

Puedes definir los datos de solicitud y respuesta dentro de un archivo. Puedes ver el ejemplo para igualar los datos desde un archivo de respuesta.

1
it('should create music and respond as in resource file', function (done) {
2
            nock('https://api.example.com')
3
                .post('/musics', {
4
                    title: 'Smoke on the water',
5
                    author: 'Deep Purple',
6
                    duration: '5.40 min.'
7
                })
8
                .reply(200, function (uri, requestBody) {
9
                    return fs.createReadStream(path.normalize(__dirname + '/resources/new_music_response.json', 'utf8'))
10
                });
11
            mediaClient.musicCreate({
12
                title: 'Smoke on the water',
13
                author: 'Deep Purple',
14
                duration: '5.40 min.'
15
            }, function (error, response) {
16
                expect(JSON.parse(response).music.id).to.eql(3164495)
17
                done()
18
            })
19
        })

Cuando se crea una pieza musical, la respuesta debe coincidir con la respuesta del archivo.

Encadenamiento de Peticiones Simuladas

También puedes encadenar una solicitud simulada en un ámbito como abajo:

1
it('it should create music and then delete', function(done) {
2
            nock('https://api.example.com')
3
                .post('/musics', {
4
                    title: 'Maps',
5
                    author: 'Maroon5',
6
                    duration: '5:00 min.'
7
                })
8
                .reply(200, {
9
                    music: {
10
                        id: 3164494,
11
                        title: 'Maps',
12
                        author: 'Maroon5',
13
                        duration: '7:00 min.'
14
                    }
15
                })
16
                .delete('/musics/' + 3164494)
17
                .reply(200, 'Music deleted')
18
19
            mediaClient.musicCreate({
20
                title: 'Maps',
21
                author: 'Maroon5',
22
                duration: '5:00 min.'
23
            }, function (error, response) {
24
                var musicId = JSON.parse(response).music.id
25
                expect(musicId).to.eql(3164494)
26
                mediaClient.musicDelete(musicId, function(error, response) {
27
                    expect(response).to.eql('Music deleted')
28
                    done()
29
                })
30
            })
31
        })

Como se puede ver, pueds simular la creación musical y la respuesta primero y luego la eliminación de música y finalmente la respuesta a otros datos.

Igualación de Cabeceras de Solicitud

En el cliente de media, Content-Type: aplication/json se proporciona en los encabezados de solicitud. Puedes probar un encabezado específico como este:

1
it('should provide token in header', function (done) {
2
    nock('https://api.example.com', {
3
        reqheaders: {
4
            'Content-Type': 'application/json'
5
        }
6
    })
7
        .get('/musics')
8
        .reply(200, 'OK')
9
10
    mediaClient.musicList(function(error, response) {
11
        expect(response).to.eql('OK')
12
        done()
13
    })
14
15
})

Cuando haces una solicitud a https://api.example.com/musics con un encabezado Content-Type: aplication/json, será interceptado por la simulación HTTP de nock de arriba, y serás capaz de probar el resultado esperado y real. También puedes probar cabeceras de respuesta de la misma manera sólo declarando el encabezado de respuesta en la sección de respuesta:

1
it('should provide specific header in response', function (done) {
2
    nock('https://api.example.com')
3
        .get('/musics')
4
        .reply(200, 'OK', {
5
            'Content-Type': 'application/json'
6
        })
7
8
    mediaClient.musicList(function(error, response) {
9
        expect(response).to.eql('OK')
10
        done()
11
    })
12
13
})

Contestar con Encabezados Predeterminados

Puedes especificar encabezados de respuesta predeterminados para las solicitudes en un ámbito mediante el uso de defaultReplyHeaders como sigue:

1
it('should provide default response header', function(done) {
2
    nock('https://api.example.com')
3
        .defaultReplyHeaders({
4
            'Content-Type': 'application/json'
5
        })
6
        .get('/musics')
7
        .reply(200, 'OK, with default response headers')
8
9
    mediaClient.musicList(function(error, response) {
10
        expect(response).to.eql('OK, with default response headers')
11
        done()
12
    })
13
})

Al llamar a la API, encontrarás por defecto encabezados de respuesta en la respuesta, incluso si no se especifica.

Operaciones de HTTP

Las operaciones de HTTP son los fundamentos de las peticiones del cliente. Puedes interceptar cualquier operación de HTTP mediante el uso de intercept:

1
scope('https://api.example.com')
2
  .intercept('/musics/1', 'DELETE')
3
  .reply(404);

Cuando intentas eliminar música con id 1, responderá 404. Puedes comparar tu resultado real con 404 para comprobar el estado de prueba. También puedes usar GET, POST, DELETE, PATCH, PUT y HEAD de la misma manera.

Repetición de la Respuesta

Normalmente, las peticiones simuladas realizadas mediante el uso de nock están disponibles por la primera vez. Puedes hacerlas disponible tanto como quieras así:

1
var scope = nock('https://api.example.com')
2
                .get('/musics')
3
                .times(2)
4
                .reply(200, 'OK with music list');
5
                
6
http.get('https://api.example.com/musics'); // "OK with music list"

7
http.get('https://apiexample.com/musics'); // "OK with music list"

8
http.get('https://apiexample.com/musics'); // "Real response from api"

Estás limitado a hacer dos peticiones como peticionnes de HTTP simuladas. Cuando haces pedidos de tres o más, automáticamente harás una solicitud real a la API.

Puerto Personalizado

También puedes proporcionar un puerto personalizado en tu URL de la API:

1
it('should handle specific port', function(done) {
2
    nock('https://api.example.com:8081')
3
        .get('/')
4
        .reply(200, 'OK with custom port')
5
    request('https://api.example.com:8081', function(error, response, body) {
6
        expect(body).to.eql('OK with custom port')
7
        done()
8
    })
9
})

Filtrado de Alcance

El filtrado de alcance se vuelve importante cuando deseas filtrar el dominio, el protocolo y el puerto para tus pruebas. Por ejemplo, la siguiente prueba te permitirá simular las solicitudes que tienen subdominios como API0, API1, API2, etcetera.

1
it('should also support sub domains', function(done) {
2
    nock('http://api.example.com', {
3
        filteringScope: function(scope) {
4
            return /^http:\/\/api[0-9]*.example.com/.test(scope);
5
        }
6
    })
7
        .get('/musics')
8
        .reply(200, 'OK with dynamic subdomains')
9
10
    request('http://api2.example.com/musics', function(error, response, body) {
11
        expect(body).to.eql('OK with dynamic subdomains')
12
        done()
13
    })
14
})

No estás limitado a una URL aquí; puedes probar los subdominios especificados en la regexp.

Filtrado de Ruta

A veces pueden variar tus parámetros de URL. Por ejemplo, los parámetros de paginación no son estáticos. En ese caso, puedes utilizar las siguientes pruebas:

1
it('should support dynamic pagination', function(done) {
2
    nock('http://api.example.com')
3
        .filteringPath(/page=[^&]*/g, 'page=123')
4
        .get('/musics?page=123')
5
        .reply(200, 'Ok response with paginate')
6
7
    request('http://api.example.com/musics?page=13', function(error, response, body) {
8
        expect(body).to.eql('Ok response with paginate')
9
        done()
10
    })
11
})

Filtrado de Cuerpo de Peticiones

Si tienes campos diferentes en el cuerpo de la solicitud, puedes utilizar filteringRequestBody para eliminar diferentes campos como:

1
it('should create movie with dynamic title', function(done) {
2
    nock('http://api.example.com')
3
        .filteringRequestBody(function(path) {
4
            return 'test'
5
        })
6
        .post('/musics', 'test')
7
        .reply(201, 'OK');
8
9
    var options = {
10
        url: 'http://api.example.com/musics',
11
        method: 'POST',
12
        body: 'author=test_author&title=test'
13
    }
14
15
    request(options, function(err, response, body) {
16
        expect(body).to.eql('OK')
17
        done()
18
    })
19
})

Aquí author puede variar, pero puedes interceptar el cuerpo de la solicitud con title=test.

Igualación de Cabecera de Petición

En este cliente, un token es usado para autenticar al usuario cliente. Es necesario comprobar el encabezado para ver un token válido en el encabezado Authorization:

1
it('should match bearer token header', function(done) {
2
    nock('https://api.example.com')
3
        .matchHeader('Authorization', /Bearer.*/)
4
        .get('/musics')
5
        .reply(200, 'Ok response with music list')
6
7
    mediaClient.musicList(function(error, response) {
8
        expect(response).to.eql('Ok response with music list')
9
        done()
10
    })
11
})

Activar o Desactivar la Simulación

En casos de prueba, puedes habilitar peticiones HTTP reales estableciendo allowUnmocked como true. Veamos el siguiente caso:

1
it('should request performed to "http://api.example.com"', function(done) {
2
    var scope = nock('http://api.example.com', {allowUnmocked: true})
3
        .get('/musics')
4
        .reply(200, 'OK');
5
    http.get('http://api.example.com/musics'); // This will intercepted by the nock

6
    http.get('http://api.example.com/videos'); // This will make real request to service

7
})

En un escenario de nock, puedes ver un escenario para la URI /musics, pero también si realizas cualquier URL diferente de /musics, será una solicitud real a la dirección URL especificada en lugar de una prueba de falla.

Otras Utilidades

Hemos cubierto cómo escribir escenarios proporcionando una petición de muestra, respuesta, encabezado, etc., y comprobar el resultado real y esperado. También puedes utilizar algunas utilidades de expectativa como isDone(), limpiar el escenario de ámbito con cleanAll(), usar un escenario de nock para siempre usando persist() y listar simulaciones pendientes dentro de su caja de prueba mediante el uso de pendingMocks().

isDone()

Digamos que has escrito un escenario de nock y ejecutas una función real en un escenario de prueba. Dentro de tu caja de prueba, puedes comprobar si se ejecuta el escenario escrito o no como sigue:

1
it('should request performed to "http://api.example.com"', function(done) {
2
    var musicList = nock('http://api.example.com')
3
        .get('/musics')
4
        .reply(200, 'OK with music list');
5
    request('http://api.example.com/musics', function(error, response){
6
        expect(musicList.isDone()).to.eql(true)
7
        done()
8
    })
9
})

Dentro del callback de la solicitud, estamos esperando esa URL en el escenario de nock (http://api.example.com/musics) para ser llamado.

cleanAll()

Si utilizar un escenario de nock con persist(), estará para siempre disponible durante la ejecución de la prueba. Puedes despejar el escenario cuando lo desees mediante este comando como se muestra en el ejemplo siguiente:

1
it('should failed due to scope clearing', function(done) {
2
    var musicList = nock('http://api.example.com')
3
        .get('/musics')
4
        .reply(200, 'OK with music list');
5
    nock.cleanAll();
6
    request('http://api.example.com/musics', function(error, response, body){
7
        expect(body).not.eql('OK with music list')
8
        done()
9
    })
10
})

En esta prueba, nuestra petición y la respuesta coincide con el escenario. Sin embargo, después de nock.cleanAll() restablecemos el escenario, y no esperamos un resultado con 'OK with music list'.

persist()

Cada escenario de nock está disponible solamente la primera vez. Si quieres hacerla vivir para siempre, puedes utilizar una prueba como esta:

1
it('should be available for infinite request', function(done) {
2
    nock('http://api.example.com')
3
        .get('/musics')
4
        .reply(200, 'OK')
5
    // First call

6
    request('https://api.example.com/musics', function(error, response, body) {
7
        expect(body).to.eql('OK')
8
        done()
9
    })
10
    // Second call

11
    request('https://api.example.com/musics', function(error, response, body) {
12
        expect(body).to.eql('OK')
13
        done()
14
    })
15
    // .......

16
})

pendingMocks()

Puedes listar los escenarios nock pendientes que no se realizan aún dentro de la prueba como sigue:

1
it('should log pending mocks', function(done) {
2
    var scope = nock('http://api.example.com')
3
        .persist()
4
        .get('/')
5
        .reply(200, 'OK');
6
    if (!scope.isDone()) {
7
        console.error('Waiting mocks: %j', scope.pendingMocks());
8
    }
9
})

También puedes ver las acciones realizadas durante la ejecución de la prueba. Puedes llegar a hacer eso utilizando el siguiente ejemplo:

1
it('should log all the request', function(done) {
2
    var musicList = nock('http://api.example.com')
3
        .log(console.log)
4
        .get('/musics')
5
        .reply(200, 'OK with music list');
6
    request('http://api.example.com/musics', function(error, response, body){
7
        expect(body).eql('OK with music list')
8
        done()
9
    })
10
})

Esta prueba generará una salida como se muestra a continuación:

1
matching GET http://api.example.com:80/musics to GET http://api.example.com:80/musics: true

Consejos Rápidos

A veces, es necesario deshabilitar la simulación para direcciones URL específicas. Por ejemplo, puedes llamar al http://tutsplus.com real, en lugar del simulado. Puedes utilizar:

1
nock.nock.enableNetConnect('tutsplus.com');

Esto hará una solicitud real dentro de las pruebas. También, puedes globalmente activar/desactivar utilizando:

1
nock.enableNetConnect();
2
nock.disableNetConnect();

Ten en cuenta que si deshabilitas net connect e intentas acceder a una dirección URL no simulada, se obtendrá una excepción NetConnectNotAllowedError en tu prueba.

Conclusión

Es una buena práctica escribir las pruebas antes de tu aplicación real, aunque el servicio dependiente no esté implementado todavía. Más allá de esto, necesitas poder ejecutar las pruebas con éxito, cuando estés sin conexión. Nock te permite simular tu solicitud y respuesta para probar la aplicación.