nock para TypeScript: guía completa con ejemplos prácticos

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

nock para TypeScript: simulación HTTP en pruebas

1. ¿Qué es nock y para qué sirve?

nock es una potente biblioteca para Node.js diseñada para interceptar y simular (mockear) peticiones HTTP. Permite a los desarrolladores imitar respuestas de servicios externos (APIs REST, microservicios de terceros, webhooks) sin necesidad de realizar llamadas de red reales. Esto es especialmente importante al escribir pruebas unitarias y de integración, donde la estabilidad y la velocidad de ejecución son críticas.

¿Por qué es útil? Imagina que tu aplicación envía peticiones a una pasarela de pago, una API del clima o una base de datos a través de HTTP. En un entorno de pruebas no quieres depender de la disponibilidad de esos servicios, su velocidad de respuesta o sus límites de peticiones. nock te permite "suplantar" esas peticiones, devolviendo datos predefinidos. Esto hace que las pruebas sean rápidas, predecibles y aisladas de factores externos. Además, puedes probar escenarios de error (timeouts, códigos 500, respuestas vacías) que son difíciles de reproducir en condiciones reales.

En el contexto de TypeScript, nock se integra perfectamente con frameworks de pruebas populares (Jest, Mocha, Vitest) y ofrece soporte de tipado, lo que hace que trabajar con ella sea cómodo y seguro. La biblioteca intercepta peticiones a nivel de Node.js (a través de los módulos http y https), por lo que funciona con cualquier cliente HTTP: axios, node-fetch, got e incluso el fetch nativo (con algunas salvedades).

2. Instalación

Para instalar nock en un proyecto TypeScript, ejecuta uno de los siguientes comandos en tu terminal:

# Con npm
npm install --save-dev nock @types/nock

# Con yarn
yarn add --dev nock @types/nock

# Con pnpm
pnpm add --save-dev nock @types/nock

Importante: @types/nock contiene las declaraciones de tipos para TypeScript. Si usas TypeScript 4.0+, los tipos podrían venir incluidos en la propia biblioteca, pero para mayor compatibilidad es mejor instalarlos explícitamente.

3. Inicio rápido

Escribamos una prueba mínima que demuestre el funcionamiento básico de nock. Supongamos que tenemos una función que obtiene datos de un usuario desde una API externa.

// userService.ts
import axios from 'axios';

export async function getUser(userId: number): Promise<{ id: number; name: string; email: string }> {
    const response = await axios.get(`https://jsonplaceholder.typicode.com/users/${userId}`);
    return response.data;
}

Ahora escribamos una prueba que simule esta petición:

// userService.test.ts
import nock from 'nock';
import { getUser } from './userService';

describe('getUser', () => {
    afterEach(() => {
        nock.cleanAll(); // Limpiamos todos los mocks después de cada prueba
    });

    it('debe devolver los datos del usuario', async () => {
        // 1. Configuramos la intercepción
        const mockUser = { id: 1, name: 'Juan Pérez', email: 'juan@ejemplo.com' };
        nock('https://jsonplaceholder.typicode.com')
            .get('/users/1')
            .reply(200, mockUser);

        // 2. Llamamos a la función bajo prueba
        const user = await getUser(1);

        // 3. Verificamos el resultado
        expect(user).toEqual(mockUser);
    });

    it('debe lanzar un error con código 500', async () => {
        nock('https://jsonplaceholder.typicode.com')
            .get('/users/1')
            .reply(500, { error: 'Error del servidor' });

        await expect(getUser(1)).rejects.toThrow();
    });
});

Este ejemplo muestra lo fácil que es simular la respuesta de un servidor y probar tanto escenarios exitosos como de error.

4. Conceptos fundamentales

Para dominar nock, es importante entender sus conceptos clave. A continuación, los explicamos con ejemplos prácticos.

4.1. Interceptor

Un interceptor es la unidad básica de nock. Se crea llamando a nock(baseUrl) y encadenando métodos para definir la petición a interceptar.

const interceptor = nock('https://api.ejemplo.com')
    .get('/ruta')
    .reply(200, { mensaje: 'Hola' });

El interceptor permanece activo hasta que se usa una vez, a menos que se configure con .persist().

4.2. Métodos HTTP soportados

nock soporta todos los métodos HTTP comunes: .get(), .post(), .put(), .patch(), .delete(), .head() y .options().

nock('https://api.ejemplo.com')
    .post('/usuarios', { nombre: 'Ana' })
    .reply(201, { id: 123 });

4.3. Filtrado de rutas y cabeceras

Puedes usar funciones para filtrar rutas o cabeceras de forma dinámica:

nock('https://api.ejemplo.com')
    .get('/usuarios')
    .query({ rol: 'admin' })
    .reply(200, [{ id: 1, nombre: 'Admin' }]);

// También puedes filtrar por cabeceras
nock('https://api.ejemplo.com', {
    reqheaders: {
        'Authorization': 'Bearer token123'
    }
})
    .get('/perfil')
    .reply(200, { email: 'admin@ejemplo.com' });

4.4. Respuestas dinámicas

Puedes devolver respuestas basadas en el cuerpo de la petición usando una función:

nock('https://api.ejemplo.com')
    .post('/calcular')
    .reply(200, (uri, requestBody) => {
        const datos = JSON.parse(requestBody);
        return { resultado: datos.a + datos.b };
    });

5. Uso avanzado

Más allá de lo básico, nock ofrece funcionalidades avanzadas para escenarios complejos.

5.1. Persistencia de interceptores

Por defecto, un interceptor se elimina después de ser usado una vez. Para que persista, usa .persist():

nock('https://api.ejemplo.com')
    .get('/recurso')
    .times(3) // Se usará exactamente 3 veces
    .reply(200, { datos: 'persistente' });

// O para que se use indefinidamente:
nock('https://api.ejemplo.com')
    .get('/recurso')
    .persist()
    .reply(200, { datos: 'siempre disponible' });

5.2. Simulación de retrasos y errores de red

Puedes simular latencia o errores de conexión:

// Simular un retraso de 2 segundos
nock('https://api.ejemplo.com')
    .get('/lento')
    .delay(2000)
    .reply(200, { mensaje: 'respuesta tardía' });

// Simular un error de conexión
nock('https://api.ejemplo.com')
    .get('/error-red')
    .replyWithError('Error de conexión simulada');

5.3. Registro de peticiones (logging)

Para depurar, puedes activar el registro de peticiones interceptadas:

nock.recorder.rec({
    dont_print: true,
    output_objects: true
});

// Después de hacer las peticiones, obtén el registro
const registros = nock.recorder.play();

5.4. Limpieza y reinicio

Es importante limpiar los interceptores entre pruebas para evitar efectos secundarios:

afterEach(() => {
    nock.cleanAll(); // Limpia todos los interceptores
    nock.restore();  // Restaura el comportamiento HTTP original (si se usó nock.activate())
});

6. Integración con TypeScript

TypeScript ofrece ventajas adicionales al usar nock, como el tipado estricto y la detección temprana de errores.

6.1. Tipado de respuestas

Puedes definir interfaces para tus respuestas simuladas y asegurarte de que coinciden con lo que espera tu aplicación:

interface Usuario {
    id: number;
    nombre: string;
    email: string;
}

const usuarioMock: Usuario = {
    id: 1,
    nombre: 'María García',
    email: 'maria@ejemplo.com'
};

nock('https://api.ejemplo.com')
    .get('/usuarios/1')
    .reply(200, usuarioMock);

6.2. Uso con async/await y promesas

nock funciona perfectamente con código asíncrono en TypeScript:

async function obtenerUsuario(id: number): Promise {
    const respuesta = await fetch(`https://api.ejemplo.com/usuarios/${id}`);
    return respuesta.json();
}

// Prueba
it('debe obtener el usuario correctamente', async () => {
    nock('https://api.ejemplo.com')
        .get('/usuarios/1')
        .reply(200, { id: 1, nombre: 'Carlos', email: 'carlos@ejemplo.com' });

    const usuario = await obtenerUsuario(1);
    expect(usuario.nombre).toBe('Carlos');
});

6.3. Configuración con Jest y Vitest

Para proyectos con Jest o Vitest, puedes configurar nock en un archivo de setup:

// setupTests.ts
import nock from 'nock';

beforeAll(() => {
    nock.disableNetConnect(); // Bloquea todas las conexiones reales
});

afterAll(() => {
    nock.enableNetConnect(); // Restaura las conexiones
    nock.cleanAll();
});

7. Ejemplos prácticos

Veamos algunos casos de uso comunes en proyectos reales.

7.1. Simular una API REST completa

// Simular múltiples endpoints de una API
const api = nock('https://api.ejemplo.com');

api.get('/productos')
    .reply(200, [
        { id: 1, nombre: 'Laptop', precio: 1200 },
        { id: 2, nombre: 'Mouse', precio: 25 }
    ]);

api.post('/productos', { nombre: 'Teclado', precio: 80 })
    .reply(201, { id: 3, nombre: 'Teclado', precio: 80 });

api.delete('/productos/1')
    .reply(204);

7.2. Probar autenticación OAuth

nock('https://auth.ejemplo.com')
    .post('/oauth/token', {
        grant_type: 'client_credentials',
        client_id: 'mi-cliente',
        client_secret: 'secreto'
    })
    .reply(200, {
        access_token: 'token-simulado',
        token_type: 'Bearer',
        expires_in: 3600
    });

7.3. Simular webhooks

// Simular la recepción de un webhook
nock('https://webhook.ejemplo.com')
    .post('/eventos', {
        tipo: 'pago_recibido',
        datos: { monto: 100, moneda: 'USD' }
    })
    .reply(200, { estado: 'procesado' });

8. Buenas prácticas y consejos

  • Siempre limpia los interceptores: Usa nock.cleanAll() en afterEach para evitar que las pruebas se afecten entre sí.
  • Bloquea conexiones reales: Usa nock.disableNetConnect() en tu configuración de pruebas para asegurarte de que no se hagan llamadas reales accidentalmente.
  • Usa .times() para controlar la cantidad de usos: Esto ayuda a verificar que tu código hace el número esperado de peticiones.
  • Mantén los mocks cerca de las pruebas: Define los interceptores dentro de cada prueba o en funciones helper específicas para mantener la claridad.
  • Prueba también escenarios negativos: No solo simules respuestas exitosas; prueba errores, timeouts y respuestas vacías.
  • Documenta los mocks complejos: Si un mock es muy elaborado, añade comentarios explicando qué está simulando.

9. Conclusión

nock es una herramienta indispensable para cualquier desarrollador que trabaje con TypeScript y necesite realizar pruebas confiables y rápidas. Su capacidad para interceptar peticiones HTTP a nivel de Node.js, combinada con el tipado de TypeScript, permite crear suites de pruebas robustas y mantenibles.

Hemos cubierto desde la instalación básica hasta técnicas avanzadas como la simulación de retrasos, el filtrado de cabeceras y la integración con frameworks de pruebas modernos. Con estos conocimientos, podrás escribir pruebas que no dependan de servicios externos, sean predecibles y cubran tanto casos de éxito como de error.

¿Listo para empezar? Instala nock en tu proyecto y comienza a escribir pruebas más sólidas hoy mismo. Recuerda: unas buenas pruebas son la base de un software de calidad.

Recommendations