Pruebas avanzadas en TypeScript: de Unit a E2E
Las pruebas no son solo una verificación del funcionamiento del código, sino la base del desarrollo profesional en TypeScript. En el nivel avanzado, pasamos de simples asserts a la construcción de pirámides de pruebas aisladas, rápidas y confiables. En este artículo, analizaremos enfoques prácticos para pruebas unitarias, de integración y e2e utilizando las mejores herramientas del ecosistema TypeScript.
1. Pruebas Unitarias Avanzadas con Jest y TypeScript
Las pruebas unitarias verifican módulos aislados. En TypeScript, la complejidad clave radica en trabajar con dependencias, tipos y asincronía.
1.1. Mocks y tipificación con jest.mocked
Utilice jest.mocked para una tipificación completa de los mocks. Esto garantiza que no llamará a un método inexistente.
import { fetchUserData } from './api';import { processUser } from './userProcessor';
jest.mock('./api');
const mockedFetchUserData = jest.mocked(fetchUserData);
describe('processUser', () => { it('debe procesar los datos del usuario', async () => { const mockUser = { id: 1, name: 'Alice', email: 'alice@test.com' }; mockedFetchUserData.mockResolvedValue(mockUser);
const result = await processUser(1);
expect(result).toEqual({ displayName: 'Alice', contact: 'alice@test.com' }); expect(mockedFetchUserData).toHaveBeenCalledWith(1); });
it('debe lanzar un error si falta el email', async () => { mockedFetchUserData.mockResolvedValue({ id: 2, name: 'Bob', email: null }); await expect(processUser(2)).rejects.toThrow('Email is required'); });});1.2. Pruebas de tipos complejos y genéricos
Para funciones con genéricos, cree tipos auxiliares de prueba para evitar la duplicación.
// Códigofunction getFirstElement<T>(arr: T[]): T | undefined { return arr[0];}
// Pruebatype TestArray = number[];const array: TestArray = [10, 20, 30];
describe('getFirstElement', () => { it('debe devolver el primer elemento numérico', () => { const result = getFirstElement(array); expect(result).toBe(10); // TypeScript infiere automáticamente el tipo number });
it('debe devolver undefined para un array vacío', () => { const result = getFirstElement([]); expect(result).toBeUndefined(); });});2. Pruebas de Integración: Base de Datos y API
Las pruebas de integración verifican la interacción de los módulos: controladores, servicios y base de datos. Para aplicaciones TypeScript en Node.js, la combinación ideal es Jest + Supertest + Testcontainers.
2.1. Configuración de la base de datos de prueba con Testcontainers
Testcontainers levanta PostgreSQL en Docker, garantizando un entorno limpio para cada prueba.
import { PostgreSqlContainer, StartedPostgreSqlContainer } from '@testcontainers/postgresql';import { Client } from 'pg';
let container: StartedPostgreSqlContainer;let client: Client;
beforeAll(async () => { container = await new PostgreSqlContainer().start(); client = new Client({ connectionString: container.getConnectionUri() }); await client.connect(); await client.query(` CREATE TABLE users ( id SERIAL PRIMARY KEY, name VARCHAR(100) NOT NULL, email VARCHAR(100) UNIQUE NOT NULL ); `);}, 30000); // tiempo de espera de 30 segundos para descargar la imagen
afterAll(async () => { await client.end(); await container.stop();});
describe('User Repository', () => { it('debe crear y devolver un usuario', async () => { const res = await client.query( `INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *`, ['Test', 'test@example.com'] ); expect(res.rows[0].name).toBe('Test'); });});2.2. Pruebas de API REST con Supertest
Iniciamos la aplicación Express y la prueba