Introducción: Por qué las pruebas en TypeScript son un must have
TypeScript es una herramienta poderosa que añade tipado estático a JavaScript. Sin embargo, incluso el sistema de tipos más estricto no garantiza que tu código funcione correctamente en tiempo de ejecución. Errores, fallos lógicos, manejo incorrecto de casos límite — todo esto queda fuera de la compilación. Aquí es donde las pruebas vienen al rescate.
En este artículo analizaremos tres niveles principales de pruebas en TypeScript: unit (unitarias), integration (integración) y e2e (end-to-end). Aprenderás en qué se diferencian, cuándo aplicar cada una y verás ejemplos reales de código. Después de leerlo, podrás configurar un pipeline de pruebas completo para tu proyecto TypeScript.
1. Pruebas unitarias: verificamos unidades atómicas de código
Las pruebas unitarias son el nivel de verificación más rápido y económico. Aíslan una función, método o clase y lo prueban sin dependencias externas (bases de datos, API, sistema de archivos).
1.1 Elección de herramienta y configuración
El framework más popular para TypeScript es Jest. Tiene soporte integrado para TypeScript a través de ts-jest o @swc/jest.
// Instalaciónnpm install --save-dev jest @types/jest ts-jest
// Inicialización de configuraciónnpx ts-jest config:initDespués de esto, aparecerá en la raíz el archivo jest.config.js con la configuración.
1.2 Ejemplo de prueba unitaria para una función
Supongamos que tenemos una función simple para calcular un descuento:
// discount.tsexport function applyDiscount(price: number, discountPercent: number): number { if (discountPercent < 0 || discountPercent > 100) { throw new Error('Porcentaje de descuento inválido'); } return price - (price * discountPercent) / 100;}Escribamos una prueba unitaria para ella:
// discount.test.tsimport { applyDiscount } from './discount';
describe('applyDiscount', () => { it('debe calcular correctamente un descuento del 10%', () => { expect(applyDiscount(1000, 10)).toBe(900); });
it('debe devolver el precio original con descuento del 0%', () => { expect(applyDiscount(500, 0)).toBe(500); });
it('debe lanzar un error con descuento mayor al 100%', () => { expect(() => applyDiscount(100, 150)).toThrow('Porcentaje de descuento inválido'); });});Ejecutamos: npx jest. Las tres pruebas deberían pasar en verde.
1.3 Mocks y stubs
Si la función accede a un servicio externo (por ejemplo, una base de datos), lo reemplazamos con un mock:
// userService.test.tsimport { getUser } from './userService';import { db } from './database';
jest.mock('./database'); // Mock automático
test('getUser devuelve un usuario por ID', async () => { (db.findUser as jest.Mock).mockResolvedValue({ id: 1, name: 'Iván' }); const user = await getUser(1); expect(user.name).toBe('Iván');});2. Pruebas de integración: verificamos la interacción de módulos
Las pruebas de integración verifican cómo diferentes partes del sistema funcionan juntas: controladores, servicios, repositorios, bases de datos. Son más lentas que las pruebas unitarias, pero brindan mayor confianza en la corrección de las conexiones.
2.1 Probando un endpoint de API con Express
Para probar solicitudes HTTP usamos la librería Supertest junto con Jest.
// Instalaciónnpm install --save-dev supertest @types/supertestSupongamos que tenemos una aplicación Express:
// app.tsimport express from 'express';import { createUser } from './controllers/userController';
export const app = express();app.use(express.json());app.post('/api/users', createUser);Prueba de integración:
// app.test.tsimport request from 'supertest';import { app } from './