Pruebas en TypeScript: de unit a e2e — guía completa
\nEl desarrollo web moderno es impensable sin pruebas de calidad. TypeScript, como superconjunto de JavaScript, proporciona herramientas potentes para la tipificación, pero no resuelve el problema de los errores lógicos. En este artículo, analizaremos tres niveles de pruebas: unit (pruebas unitarias), integration (pruebas de integración) y e2e (pruebas de extremo a extremo). Aprenderá a configurar el entorno, escribir pruebas efectivas en TypeScript e integrarlas en un pipeline de CI/CD.
\n\n1. Pruebas unitarias: la base de la pirámide de pruebas
\nLas pruebas unitarias verifican módulos aislados (funciones, clases, métodos) sin dependencias externas. Para TypeScript, la mejor opción es Jest — admite TypeScript de forma nativa y proporciona una API rica para mocks y aserciones.
\n\nConfiguración de Jest para TypeScript
\nnpm install --save-dev jest @types/jest ts-jest\nnpx ts-jest config:init\nEl archivo jest.config.ts debe contener:
export default {\n preset: 'ts-jest',\n testEnvironment: 'node',\n roots: ['<rootDir>/src'],\n testMatch: ['**/__tests__/**/*.test.ts']\n};\n\nEjemplo de prueba unitaria de una función
\nSupongamos que tenemos una función para calcular un descuento:
\n// src/utils/discount.ts\nexport function calculateDiscount(price: number, percent: number): number {\n if (percent < 0 || percent > 100) throw new Error('Invalid percent');\n return price - (price * percent) / 100;\n}\nPrueba para ella:
\n// src/__tests__/discount.test.ts\nimport { calculateDiscount } from '../utils/discount';\n\ndescribe('calculateDiscount', () => {\n it('debería devolver 80 para un precio de 100 y un descuento del 20%', () => {\n expect(calculateDiscount(100, 20)).toBe(80);\n });\n\n it('debería lanzar un error con un porcentaje inválido', () => {\n expect(() => calculateDiscount(100, 150)).toThrow('Invalid percent');\n });\n\n it('debería devolver 0 con un descuento del 100%', () => {\n expect(calculateDiscount(50, 100)).toBe(0);\n });\n});\n\nTrabajo con mocks
\nPara aislar las pruebas, use mocks. Ejemplo con un servicio que depende de una base de datos:
\n// src/services/user.service.ts\nimport { db } from '../database';\n\nexport class UserService {\n async getUser(id: number) {\n const user = await db.findUser(id);\n if (!user) throw new Error('User not found');\n return user;\n }\n}\n// src/__tests__/user.service.test.ts\nimport { UserService } from '../services/user.service';\nimport { db } from '../database';\n\njest.mock('../database');\n\ndescribe('UserService', () => {\n it('debería devolver un usuario', async () => {\n const mockUser = { id: 1, name: 'Alice' };\n (db.findUser as jest.Mock).mockResolvedValue(mockUser);\n\n const service = new UserService();\n const result = await service.getUser(1);\n expect(result).toEqual(mockUser);\n });\n});\n\n2. Pruebas de integración: verificación de la interacción entre componentes
\nLas pruebas de integración verifican cómo los módulos trabajan juntos: rutas de API, bases de datos, servicios externos. Para aplicaciones TypeScript en Node.js, a menudo se usa Supertest junto con Jest.
\n\nConfiguración de una prueba de integración para Express/NestJS
\nnpm install --save-dev supertest @types/supertest\nEjemplo de prueba para una API REST (NestJS):
\n// src/__tests__/app.integration.test.ts\nimport { Test, TestingModule } from '@nestjs/testing';\nimport { INestApplication } from '@nestjs/common';\nimport * as request from 'supertest';\nimport { AppModule } from '../app.module';\n\ndescribe('AppController (e2e)', () => {\n let app: INestApplication;\n\n beforeAll(async () => {\n const moduleFixture: TestingModule = await Test.createTestingModule({\n imports: [AppModule],\n }).compile();\n\n a