Saltar al contenidoPedro Farbo
Lección 17 / 2555 min

Pruebas Unitarias

Pruebas Unitarias

Aprende a escribir pruebas unitarias con Jest.

Instalación

bash
npm install -D jest ts-jest @types/jestnpx ts-jest config:init

Configuración

javascript
// jest.config.jsmodule.exports = {  preset: 'ts-jest',  testEnvironment: 'node',  roots: ['<rootDir>/src'],  testMatch: ['**/*.spec.ts', '**/*.test.ts'],  moduleNameMapper: {    '@/(.*)': '<rootDir>/src/$1',  },  collectCoverageFrom: [    'src/**/*.ts',    '!src/**/*.d.ts',  ],  coverageDirectory: 'coverage',  clearMocks: true,};

Scripts

json
{  "scripts": {    "test": "jest",    "test:watch": "jest --watch",    "test:coverage": "jest --coverage"  }}

Estructura de Pruebas

src/
├── services/
│   ├── product.service.ts
│   └── __tests__/
│       └── product.service.spec.ts
├── utils/
│   ├── helpers.ts
│   └── __tests__/
│       └── helpers.spec.ts

Prueba Básica

typescript
// src/utils/__tests__/helpers.spec.tsimport { calculateDiscount, generateSlug } from '../helpers'; describe('Helpers', () => {  describe('calculateDiscount', () => {    it('debe calcular el descuento correctamente', () => {      const result = calculateDiscount(100, 20);      expect(result).toBe(80);    });     it('debe retornar el precio original si el descuento es 0', () => {      const result = calculateDiscount(100, 0);      expect(result).toBe(100);    });     it('debe lanzar error si el descuento es mayor a 100', () => {      expect(() => calculateDiscount(100, 150)).toThrow();    });  });   describe('generateSlug', () => {    it('debe convertir a minúsculas y reemplazar espacios', () => {      expect(generateSlug('iPhone 15 Pro')).toBe('iphone-15-pro');    });     it('debe eliminar caracteres especiales', () => {      expect(generateSlug('Cámara 4K!')).toBe('camara-4k');    });  });});

Mocking con Jest

typescript
// src/services/__tests__/product.service.spec.tsimport { ProductService } from '../product.service';import { prisma } from '../../config/database';import { AppError } from '../../errors/AppError'; // Mock de Prismajest.mock('../../config/database', () => ({  prisma: {    product: {      findMany: jest.fn(),      findUnique: jest.fn(),      create: jest.fn(),      update: jest.fn(),      delete: jest.fn(),      count: jest.fn(),    },  },})); describe('ProductService', () => {  let service: ProductService;   beforeEach(() => {    service = new ProductService();    jest.clearAllMocks();  });   describe('findById', () => {    it('debe retornar el producto si existe', async () => {      const mockProduct = { id: '123', name: 'iPhone', price: 999 };      (prisma.product.findUnique as jest.Mock).mockResolvedValue(mockProduct);       const result = await service.findById('123');       expect(result).toEqual(mockProduct);      expect(prisma.product.findUnique).toHaveBeenCalledWith({        where: { id: '123' },        include: { category: true },      });    });     it('debe lanzar AppError si no existe', async () => {      (prisma.product.findUnique as jest.Mock).mockResolvedValue(null);       await expect(service.findById('999')).rejects.toThrow(AppError);      await expect(service.findById('999')).rejects.toThrow('Producto no encontrado');    });  });   describe('create', () => {    const createData = { name: 'MacBook', price: 1999, categoryId: 'cat-1' };     it('debe crear el producto con slug', async () => {      (prisma.product.findUnique as jest.Mock).mockResolvedValue(null);      (prisma.product.create as jest.Mock).mockResolvedValue({        id: '1',        ...createData,        slug: 'macbook',      });       const result = await service.create(createData);       expect(result.slug).toBe('macbook');    });     it('debe lanzar error si el producto ya existe', async () => {      (prisma.product.findUnique as jest.Mock).mockResolvedValue({ id: '1' });       await expect(service.create(createData)).rejects.toThrow('ya existe');    });  });});

Ejecutar Pruebas

bash
# Todas las pruebasnpm test # Con watch modenpm run test:watch # Con coberturanpm run test:coverage

Resumen

  • ✅ Jest configurado con TypeScript
  • ✅ Estructura de pruebas organizada
  • ✅ Mocking de dependencias
  • ✅ Pruebas de error handling

Próxima clase: Pruebas de Integración! 🚀

¿Te gustó el contenido? ¡Tu contribución ayuda a mantener todo online y gratuito!

PIX:0737160d-e98f-4a65-8392-5dba70e7ff3e