TypeScript

TypeScript

Tutorial TypeScript: Testing unitario en TypeScript

Aprende a realizar testing unitario en TypeScript con Jest. Configura tu entorno y aplica buenas prácticas para mejorar la calidad del código.

Aprende TypeScript y certifícate

Importancia del testing en TypeScript

El testing o pruebas de software constituye una práctica fundamental en el desarrollo moderno que adquiere un valor especial cuando trabajamos con TypeScript. A diferencia de JavaScript, TypeScript incorpora un sistema de tipos estático que nos ayuda a detectar errores durante la fase de compilación, pero esto no elimina la necesidad de realizar pruebas exhaustivas de nuestro código.

Las pruebas unitarias en TypeScript nos permiten verificar que cada componente individual de nuestro código funciona correctamente de manera aislada. Esto es especialmente relevante en un lenguaje tipado como TypeScript, donde podemos aprovechar las ventajas del sistema de tipos para crear pruebas más robustas y con mayor cobertura.

Beneficios específicos del testing en TypeScript

  • Detección temprana de errores: Las pruebas unitarias complementan el sistema de tipos de TypeScript, permitiéndonos encontrar errores que el compilador no puede detectar.
// El compilador verifica que los tipos sean correctos
function sum(a: number, b: number): number {
  return a + b;
}

// Pero no puede detectar errores de lógica como este:
function subtract(a: number, b: number): number {
  return a + b; // Error lógico: debería ser a - b
}
  • Documentación viva: Las pruebas actúan como una forma de documentación ejecutable que muestra cómo debe utilizarse cada componente de tu aplicación.
// El test documenta cómo debe usarse la función
describe('Calculator', () => {
  it('should add two numbers correctly', () => {
    // Muestra claramente cómo debe usarse la función sum
    expect(sum(2, 3)).toBe(5);
  });
});

Refactorización segura: Al tener pruebas que verifican el comportamiento esperado, podemos modificar y mejorar nuestro código con la confianza de que no estamos introduciendo errores.

Integración con el sistema de tipos: TypeScript nos permite crear pruebas más precisas aprovechando la información de tipos.

// Podemos probar comportamientos específicos según los tipos
interface User {
  id: number;
  name: string;
  email: string;
}

function getUserName(user: User): string {
  return user.name;
}

// El test puede aprovechar la interfaz User
it('should extract user name correctly', () => {
  const testUser: User = { id: 1, name: 'John', email: 'john@example.com' };
  expect(getUserName(testUser)).toBe('John');
});

Tipos de pruebas en el desarrollo con TypeScript

Pruebas unitarias: Verifican el funcionamiento correcto de funciones individuales o componentes aislados.

Pruebas de integración: Comprueban que diferentes partes del sistema funcionan correctamente cuando se combinan.

Pruebas end-to-end: Simulan el comportamiento de un usuario real interactuando con la aplicación completa.

En el contexto de TypeScript, las pruebas unitarias son particularmente valiosas porque nos permiten verificar tanto la lógica como la conformidad de tipos de nuestras funciones y clases.

Mejora de la calidad del código

El testing en TypeScript no solo nos ayuda a detectar errores, sino que también nos impulsa a escribir mejor código. Al diseñar nuestro código para que sea fácilmente testeable, naturalmente tendemos a:

  • Crear funciones puras con responsabilidades bien definidas
  • Establecer interfaces claras entre componentes
  • Reducir el acoplamiento entre diferentes partes del sistema
  • Mejorar la modularidad general de la aplicación
// Código difícil de probar
function processUserData() {
  const userData = fetchUserFromDatabase(); // Dependencia externa
  const processed = complexCalculation(userData);
  saveToDatabase(processed); // Efecto secundario
  return processed;
}

// Código más testeable
function processUserData(userData: UserData): ProcessedData {
  return complexCalculation(userData);
}
// Ahora podemos probar processUserData de forma aislada

Herramientas populares para testing en TypeScript

El ecosistema de TypeScript ofrece diversas herramientas para implementar pruebas unitarias:

  • Jest: Framework de testing completo con soporte nativo para TypeScript mediante ts-jest.
  • Mocha: Framework flexible que puede combinarse con chai y ts-node.
  • Jasmine: Framework de BDD (Behavior-Driven Development) que funciona bien con TypeScript.
  • Vitest: Framework moderno optimizado para proyectos basados en Vite.

Cada una de estas herramientas tiene sus propias ventajas, pero todas permiten escribir pruebas efectivas para código TypeScript.

Integración con el flujo de trabajo de desarrollo

Las pruebas unitarias en TypeScript se integran perfectamente en el flujo de trabajo de desarrollo moderno:

  • Desarrollo guiado por pruebas (TDD): Escribir primero las pruebas y luego el código que las satisface.
  • Integración continua: Ejecutar automáticamente las pruebas en cada cambio del código.
  • Cobertura de código: Medir qué porcentaje del código está cubierto por pruebas.
// Ejemplo de TDD en TypeScript
// 1. Primero escribimos el test
describe('StringUtils', () => {
  it('should capitalize the first letter of a string', () => {
    expect(capitalize('hello')).toBe('Hello');
  });
});

// 2. Luego implementamos la función para que pase el test
function capitalize(str: string): string {
  if (str.length === 0) return str;
  return str.charAt(0).toUpperCase() + str.slice(1);
}

El testing en TypeScript no es solo una buena práctica, sino una necesidad para desarrollar software robusto y mantenible. Aunque el sistema de tipos de TypeScript nos ayuda a evitar muchos errores comunes, las pruebas unitarias siguen siendo esenciales para garantizar que nuestro código funciona correctamente en todos los escenarios posibles.

Configuración del entorno de pruebas con Jest

Una vez comprendida la importancia del testing en TypeScript, el siguiente paso es configurar un entorno de pruebas adecuado. Jest es uno de los frameworks de testing más populares para JavaScript y TypeScript debido a su facilidad de uso y su potente conjunto de funcionalidades.

Instalación de las dependencias necesarias

Para comenzar a utilizar Jest con TypeScript, necesitamos instalar varias dependencias en nuestro proyecto:

npm install --save-dev jest @types/jest ts-jest

Estas dependencias cumplen funciones específicas:

  • Jest: El framework de testing principal
  • @types/jest: Proporciona las definiciones de tipos para Jest
  • ts-jest: Un transformador que permite a Jest procesar archivos TypeScript

Jest configuration example

Configuración básica de Jest

Para que Jest funcione correctamente con TypeScript, necesitamos crear un archivo de configuración. Podemos hacerlo de dos maneras:

  • 1. Configuración automática con el asistente de ts-jest:
npx ts-jest config:init

Este comando generará un archivo jest.config.js básico en la raíz de tu proyecto.

Jest configuration example

  • 2. Configuración manual creando el archivo jest.config.js:
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  roots: ['<rootDir>/src'],
  testMatch: ['**/__tests__/**/*.ts?(x)', '**/?(*.)+(spec|test).ts?(x)'],
  transform: {
    '^.+\\.tsx?$': 'ts-jest'
  }
};

Esta configuración indica a Jest que:

  • Use el preset de ts-jest para procesar archivos TypeScript
  • Ejecute las pruebas en un entorno de Node.js
  • Busque archivos de prueba en el directorio src
  • Considere como pruebas los archivos que terminen en .spec.ts, .test.ts y similares
  • Use ts-jest para transformar los archivos TypeScript antes de ejecutar las pruebas

Actualización del archivo tsconfig.json

Es recomendable actualizar el archivo tsconfig.json para incluir configuraciones específicas para testing:

{
  "compilerOptions": {
    "target": "es2016",
    "module": "commonjs",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true,
    "types": ["jest", "node"]
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

La propiedad "types": ["jest", "node"] asegura que TypeScript reconozca los tipos de Jest y Node.js, lo que mejora el autocompletado y la verificación de tipos en tu editor.

Configuración de scripts en package.json

Para facilitar la ejecución de las pruebas, es útil añadir scripts en el archivo package.json:

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

Estos scripts nos permiten:

  • Ejecutar todas las pruebas con npm test
  • Ejecutar las pruebas en modo observador (se vuelven a ejecutar cuando cambia el código) con npm run test:watch
  • Generar un informe de cobertura con npm run test:coverage

Estructura de directorios recomendada

Una estructura de directorios bien organizada facilita la gestión de las pruebas:

proyecto/
├── src/
│   ├── components/
│   │   ├── Calculator.ts
│   │   └── __tests__/
│   │       └── Calculator.test.ts
│   └── utils/
│       ├── stringUtils.ts
│       └── __tests__/
│           └── stringUtils.test.ts
├── jest.config.js
├── package.json
└── tsconfig.json

Existen dos enfoques principales para organizar los archivos de prueba:

  • Enfoque 1: Colocar los tests en directorios __tests__ junto a los archivos que prueban
  • Enfoque 2: Mantener los tests junto a los archivos de implementación con extensiones .test.ts o .spec.ts

Ambos enfoques son válidos y Jest reconocerá los archivos de prueba en cualquiera de estas estructuras.

Configuración avanzada

Para proyectos más complejos, podemos personalizar aún más la configuración de Jest:

module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  roots: ['<rootDir>/src'],
  testMatch: ['**/__tests__/**/*.ts?(x)', '**/?(*.)+(spec|test).ts?(x)'],
  transform: {
    '^.+\\.tsx?$': 'ts-jest'
  },
  collectCoverageFrom: [
    'src/**/*.{ts,tsx}',
    '!src/**/*.d.ts',
    '!src/**/__tests__/**'
  ],
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80
    }
  },
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1'
  }
};

Esta configuración avanzada incluye:

  • Umbrales de cobertura que hacen fallar las pruebas si no se alcanza un 80% de cobertura
  • Mapeo de módulos para facilitar las importaciones usando alias como @/utils en lugar de rutas relativas
  • Exclusión de archivos de definición de tipos (.d.ts) de la cobertura

Verificación de la configuración

Para verificar que nuestra configuración funciona correctamente, podemos crear un archivo de prueba simple:

// src/utils/math.ts
export function sum(a: number, b: number): number {
  return a + b;
}

// src/utils/__tests__/math.test.ts
import { sum } from '../math';

describe('Math utilities', () => {
  it('should add two numbers correctly', () => {
    expect(sum(2, 3)).toBe(5);
    expect(sum(-1, 1)).toBe(0);
    expect(sum(0, 0)).toBe(0);
  });
});

Ejecutamos las pruebas con:

npm test

Si todo está configurado correctamente, deberíamos ver un resultado similar a:

 PASS  src/utils/__tests__/math.test.ts
  Math utilities
    ✓ should add two numbers correctly (3 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.5 s

Configuración para diferentes entornos

Jest permite configurar diferentes entornos según el tipo de código que estemos probando:

Para aplicaciones de Node.js:

Para aplicaciones de navegador:

El entorno jsdom proporciona una implementación del DOM que permite probar código que interactúa con el navegador.

Con esta configuración básica, ya estamos listos para comenzar a escribir pruebas unitarias efectivas para nuestro código TypeScript utilizando Jest como framework de testing.

Estructura básica de un test unitario en Jest

Una vez configurado el entorno de pruebas con Jest para TypeScript, es momento de aprender cómo estructurar correctamente un test unitario. La estructura clara y consistente de las pruebas es fundamental para mantener un conjunto de tests comprensible y mantenible a largo plazo.

Anatomía de un test unitario

Un test unitario en Jest sigue una estructura básica que consta de varios elementos clave:

// Importaciones necesarias
import { functionToTest } from '../path/to/function';

// Bloque de descripción
describe('NombreDeLaFunción', () => {
  // Caso de prueba individual
  it('should do something specific', () => {
    // 1. Preparación (Arrange)
    const input = 'test input';
    
    // 2. Ejecución (Act)
    const result = functionToTest(input);
    
    // 3. Verificación (Assert)
    expect(result).toBe('expected output');
  });
});

Esta estructura sigue el patrón AAA (Arrange-Act-Assert), que es una práctica recomendada para organizar el código dentro de cada test:

  • Arrange: Preparar los datos y condiciones necesarias para la prueba
  • Act: Ejecutar la función o método que se está probando
  • Assert: Verificar que el resultado es el esperado

Bloques describe e it

Los bloques describe e it son los componentes fundamentales de la estructura de un test en Jest:

describe('Calculator', () => {
  describe('add method', () => {
    it('should add two positive numbers correctly', () => {
      expect(calculator.add(2, 3)).toBe(5);
    });

    it('should handle negative numbers', () => {
      expect(calculator.add(-1, -3)).toBe(-4);
    });
  });
});
  • El bloque describe agrupa tests relacionados y puede anidarse para crear una jerarquía lógica
  • El bloque it (o su alias test) define un caso de prueba individual
  • Los nombres de estos bloques deben formar una frase legible que describa claramente lo que se está probando

Matchers: verificación de resultados

Los matchers son métodos que permiten verificar valores de diferentes maneras. Jest proporciona una amplia variedad de matchers para diferentes tipos de comparaciones:

it('should demonstrate different matchers', () => {
  // Igualdad exacta
  expect(2 + 2).toBe(4);
  
  // Igualdad de objetos (compara contenido)
  expect({ name: 'John' }).toEqual({ name: 'John' });
  
  // Comprobaciones de verdad/falsedad
  expect(true).toBeTruthy();
  expect(false).toBeFalsy();
  expect(null).toBeNull();
  expect(undefined).toBeUndefined();
  
  // Números
  expect(10).toBeGreaterThan(5);
  expect(5).toBeLessThanOrEqual(10);
  
  // Strings
  expect('Hello World').toMatch(/World/);
  
  // Arrays
  expect([1, 2, 3]).toContain(2);
  
  // Excepciones
  expect(() => {
    throw new Error('Test error');
  }).toThrow('Test error');
});

Estos matchers hacen que las aserciones sean expresivas y fáciles de leer, lo que mejora la claridad de las pruebas.

Ejemplo práctico: probar una función de utilidad

Veamos un ejemplo completo de cómo probar una función de utilidad en TypeScript:

// src/utils/stringUtils.ts
export function capitalizeFirstLetter(str: string): string {
  if (!str) return '';
  return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
}

// src/utils/__tests__/stringUtils.test.ts
import { capitalizeFirstLetter } from '../stringUtils';

describe('stringUtils', () => {
  describe('capitalizeFirstLetter', () => {
    it('should capitalize the first letter of a string', () => {
      // Arrange
      const input = 'hello';
      
      // Act
      const result = capitalizeFirstLetter(input);
      
      // Assert
      expect(result).toBe('Hello');
    });
    
    it('should handle empty strings', () => {
      expect(capitalizeFirstLetter('')).toBe('');
    });
    
    it('should convert rest of string to lowercase', () => {
      expect(capitalizeFirstLetter('hELLO')).toBe('Hello');
    });
    
    it('should handle strings with one character', () => {
      expect(capitalizeFirstLetter('a')).toBe('A');
    });
  });
});

Observa cómo cada caso de prueba se centra en un aspecto específico de la función, lo que facilita identificar problemas cuando una prueba falla.

Configuración y limpieza con beforeEach y afterEach

Para pruebas que requieren una configuración común o limpieza después de cada test, Jest proporciona las funciones beforeEach y afterEach:

describe('UserService', () => {
  let userService: UserService;
  let mockDatabase: MockDatabase;
  
  beforeEach(() => {
    // Se ejecuta antes de cada prueba
    mockDatabase = new MockDatabase();
    userService = new UserService(mockDatabase);
  });
  
  afterEach(() => {
    // Se ejecuta después de cada prueba
    mockDatabase.reset();
  });
  
  it('should create a user successfully', () => {
    const user = { name: 'John', email: 'john@example.com' };
    const result = userService.createUser(user);
    expect(result.success).toBeTruthy();
    expect(mockDatabase.users.length).toBe(1);
  });
});

También existen las variantes beforeAll y afterAll que se ejecutan una sola vez antes o después de todas las pruebas en un bloque describe.

Pruebas asíncronas

Para probar código asíncrono, Jest ofrece varias opciones:

// Usando async/await
it('should fetch user data asynchronously', async () => {
  const userData = await userService.fetchUserData(1);
  expect(userData.name).toBe('John Doe');
});

// Usando promesas
it('should fetch user data asynchronously', () => {
  return userService.fetchUserData(1).then(userData => {
    expect(userData.name).toBe('John Doe');
  });
});

// Usando done callback (menos recomendado)
it('should fetch user data asynchronously', (done) => {
  userService.fetchUserData(1).then(userData => {
    expect(userData.name).toBe('John Doe');
    done();
  });
});

El enfoque con async/await es generalmente el más limpio y fácil de leer para pruebas asíncronas.

Organización de tests con describe.only y it.skip

Durante el desarrollo, a veces necesitamos enfocarnos en pruebas específicas:

// Solo ejecuta este bloque de pruebas
describe.only('Feature under development', () => {
  it('should work correctly', () => {
    // ...
  });
});

// Omite esta prueba temporalmente
it.skip('is a test I need to fix later', () => {
  // ...
});

Estas funciones son útiles durante el desarrollo, pero deben eliminarse antes de enviar el código a producción.

Buenas prácticas para escribir tests unitarios

  • Independencia: Cada test debe poder ejecutarse de forma aislada sin depender de otros tests
  • Determinismo: Los tests deben producir el mismo resultado cada vez que se ejecutan
  • Claridad: Los nombres de los tests deben describir claramente lo que se está probando
  • Concisión: Cada test debe enfocarse en probar una sola cosa
  • Cobertura: Los tests deben cubrir tanto los casos normales como los casos límite
// Ejemplo de tests bien estructurados
describe('divideNumbers', () => {
  it('should divide two positive numbers correctly', () => {
    expect(divideNumbers(10, 2)).toBe(5);
  });
  
  it('should throw an error when dividing by zero', () => {
    expect(() => divideNumbers(10, 0)).toThrow('Cannot divide by zero');
  });
  
  it('should handle decimal results', () => {
    expect(divideNumbers(10, 3)).toBeCloseTo(3.333, 3);
  });
});

Siguiendo esta estructura básica y estas prácticas, podrás crear tests unitarios efectivos que ayuden a mantener la calidad de tu código TypeScript a lo largo del tiempo.

Tipado en pruebas unitarias de Jest

El tipado en las pruebas unitarias de Jest con TypeScript añade una capa adicional de seguridad y claridad a nuestros tests. Aprovechar el sistema de tipos de TypeScript nos permite detectar errores potenciales durante el desarrollo de las pruebas, mejorar la documentación del código y facilitar el mantenimiento a largo plazo.

Beneficios del tipado en pruebas

Cuando aplicamos correctamente el tipado en nuestras pruebas, obtenemos varias ventajas:

  • Detección temprana de errores: El compilador de TypeScript puede identificar problemas antes de ejecutar los tests
  • Mejor autocompletado: El editor sugiere propiedades y métodos válidos mientras escribimos
  • Documentación implícita: Los tipos comunican la estructura esperada de los datos
  • Refactorización más segura: Al cambiar tipos en el código fuente, las pruebas también necesitarán actualizarse

Tipado de funciones mock

Uno de los escenarios más comunes donde el tipado es crucial es al crear mocks de funciones o dependencias:

// Función que queremos probar
function processUser(user: User, logger: Logger): UserResult {
  logger.log(`Processing user: ${user.name}`);
  return { id: user.id, processed: true };
}

// Definición de tipos
interface User {
  id: number;
  name: string;
  email: string;
}

interface UserResult {
  id: number;
  processed: boolean;
}

interface Logger {
  log: (message: string) => void;
  error: (message: string, error?: Error) => void;
}

// Test con tipado correcto
describe('processUser', () => {
  it('should process a user correctly', () => {
    // Mock tipado de Logger
    const mockLogger: jest.Mocked<Logger> = {
      log: jest.fn(),
      error: jest.fn()
    };
    
    const testUser: User = {
      id: 1,
      name: 'John Doe',
      email: 'john@example.com'
    };
    
    const result = processUser(testUser, mockLogger);
    
    expect(result.processed).toBe(true);
    expect(mockLogger.log).toHaveBeenCalledWith('Processing user: John Doe');
  });
});

Observa cómo utilizamos jest.Mocked<Logger> para tipar correctamente nuestro mock, asegurando que implementa todos los métodos requeridos por la interfaz Logger.

Uso de jest.Mock y MockInstance

Jest proporciona tipos específicos para trabajar con mocks:

// Servicio que queremos mockear
class UserService {
  async findById(id: number): Promise<User | null> {
    // Implementación real que haría una llamada a la base de datos
    return null;
  }
  
  async create(userData: Omit<User, 'id'>): Promise<User> {
    // Implementación real
    return { id: 1, ...userData };
  }
}

// Test con jest.Mock
jest.mock('../services/UserService');

describe('UserController', () => {
  // Tipado del mock
  const MockedUserService = UserService as jest.MockedClass<typeof UserService>;
  
  beforeEach(() => {
    // Limpiamos todos los mocks antes de cada test
    MockedUserService.mockClear();
  });
  
  it('should return user when found', async () => {
    // Configuramos el comportamiento del mock tipado
    const mockUser: User = { id: 1, name: 'John', email: 'john@example.com' };
    MockedUserService.prototype.findById.mockResolvedValue(mockUser);
    
    const controller = new UserController(new UserService());
    const result = await controller.getUser(1);
    
    expect(result).toEqual(mockUser);
    expect(MockedUserService.prototype.findById).toHaveBeenCalledWith(1);
  });
});

El uso de jest.MockedClass<typeof UserService> nos proporciona tipado completo para la clase mockeada, incluyendo sus métodos y propiedades.

Tipado de funciones spy

Cuando necesitamos espiar funciones existentes, también podemos aprovechar el tipado:

import * as fileUtils from '../utils/fileUtils';

describe('ConfigLoader', () => {
  it('should read configuration from file', () => {
    // Creamos un spy tipado
    const readFileSpy = jest.spyOn(fileUtils, 'readFile');
    
    // Configuramos el comportamiento del spy
    readFileSpy.mockResolvedValue('{"apiKey": "test-key"}');
    
    const configLoader = new ConfigLoader();
    return configLoader.load().then(config => {
      expect(config.apiKey).toBe('test-key');
      expect(readFileSpy).toHaveBeenCalledWith('config.json');
      
      // Restauramos la implementación original
      readFileSpy.mockRestore();
    });
  });
});

El spy creado con jest.spyOn() mantiene el tipado de la función original, lo que nos ayuda a evitar errores al configurar su comportamiento.

Tipado de datos de prueba

Es una buena práctica definir tipos para los datos de prueba, especialmente cuando se utilizan en múltiples tests:

// Tipos para datos de prueba
interface TestUser {
  id: number;
  name: string;
  email: string;
  role: 'admin' | 'user';
}

// Factory function tipada
function createTestUser(overrides?: Partial<TestUser>): TestUser {
  return {
    id: 1,
    name: 'Test User',
    email: 'test@example.com',
    role: 'user',
    ...overrides
  };
}

describe('UserPermissions', () => {
  it('should grant admin access to admin users', () => {
    const adminUser = createTestUser({ role: 'admin' });
    const permissions = new UserPermissions(adminUser);
    
    expect(permissions.canAccessAdminPanel()).toBe(true);
  });
  
  it('should deny admin access to regular users', () => {
    const regularUser = createTestUser({ role: 'user' });
    const permissions = new UserPermissions(regularUser);
    
    expect(permissions.canAccessAdminPanel()).toBe(false);
  });
});

Este enfoque facilita la creación de datos de prueba consistentes y bien tipados.

Tipado de matchers personalizados

Jest permite extender sus capacidades con matchers personalizados, que también pueden beneficiarse del tipado:

// Extendemos los matchers de Jest
declare global {
  namespace jest {
    interface Matchers<R> {
      toBeValidUser(): R;
      toHavePermission(permission: string): R;
    }
  }
}

// Implementamos los matchers personalizados
expect.extend({
  toBeValidUser(received) {
    const isValid = received && 
                   typeof received.id === 'number' &&
                   typeof received.name === 'string';
    
    return {
      message: () => `expected ${received} to be a valid user`,
      pass: isValid
    };
  },
  
  toHavePermission(received, permission: string) {
    const hasPermission = received?.permissions?.includes(permission);
    
    return {
      message: () => `expected ${received} to have permission "${permission}"`,
      pass: hasPermission
    };
  }
});

// Uso de los matchers tipados
it('should create a valid user', () => {
  const user = userService.createUser('John');
  expect(user).toBeValidUser();
  expect(user).toHavePermission('read');
});

Al declarar los tipos para nuestros matchers personalizados, obtenemos autocompletado y verificación de tipos al usarlos.

Tipado de mocks para módulos externos

Cuando necesitamos mockear módulos externos, también podemos aplicar tipado:

// Mockeamos axios
jest.mock('axios');
import axios from 'axios';

// Tipamos el mock
const mockedAxios = axios as jest.Mocked<typeof axios>;

describe('ApiClient', () => {
  it('should fetch data from API', async () => {
    // Configuramos el comportamiento del mock tipado
    const responseData = { id: 1, name: 'Test' };
    mockedAxios.get.mockResolvedValue({ data: responseData });
    
    const apiClient = new ApiClient();
    const result = await apiClient.fetchData('/test');
    
    expect(result).toEqual(responseData);
    expect(mockedAxios.get).toHaveBeenCalledWith('/test');
  });
});

El tipo jest.Mocked<typeof axios> asegura que nuestro mock tenga todos los métodos y propiedades del módulo original con los tipos correctos.

Tipado de contextos de prueba

Para tests que comparten un contexto común, podemos definir interfaces para ese contexto:

// Definimos el tipo para el contexto de prueba
interface TestContext {
  database: MockDatabase;
  userService: UserService;
  testUser: User;
}

describe('User management', () => {
  // Función tipada para configurar el contexto
  const setupTest = (): TestContext => {
    const database = new MockDatabase();
    const userService = new UserService(database);
    const testUser = { id: 1, name: 'Test User', email: 'test@example.com' };
    
    return { database, userService, testUser };
  };
  
  it('should create a user', () => {
    // Contexto tipado
    const { database, userService, testUser } = setupTest();
    
    userService.createUser(testUser);
    
    expect(database.users).toContain(testUser);
  });
  
  it('should delete a user', () => {
    const { database, userService, testUser } = setupTest();
    database.users.push(testUser);
    
    userService.deleteUser(testUser.id);
    
    expect(database.users).not.toContain(testUser);
  });
});

Este enfoque proporciona un contexto bien tipado para cada test, facilitando el acceso a los objetos necesarios.

El uso adecuado del tipado en nuestras pruebas unitarias con Jest y TypeScript no solo mejora la calidad de las pruebas, sino que también hace que el proceso de desarrollo sea más eficiente y menos propenso a errores. Al combinar las capacidades de Jest con el sistema de tipos de TypeScript, creamos una red de seguridad más robusta para nuestro código.

// jest.config.js
module.exports = {
// ...
testEnvironment: 'jsdom'
};
// jest.config.js
module.exports = {
// ...
testEnvironment: 'node'
};
Aprende TypeScript online

Otros ejercicios de programación de TypeScript

Evalúa tus conocimientos de esta lección Testing unitario en TypeScript con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Funciones

TypeScript
Test

Reto composición de funciones

TypeScript
Código

Reto tipos especiales

TypeScript
Código

Reto tipos genéricos

TypeScript
Código

Módulos

TypeScript
Test

Polimorfismo

TypeScript
Código

Funciones TypeScript

TypeScript
Código

Interfaces

TypeScript
Puzzle

Funciones puras

TypeScript
Puzzle

Reto namespaces

TypeScript
Código

Funciones flecha

TypeScript
Puzzle

Polimorfismo

TypeScript
Test

Operadores

TypeScript
Test

Conversor de unidades

TypeScript
Proyecto

Funciones flecha

TypeScript
Test

Control de flujo

TypeScript
Código

Herencia

TypeScript
Puzzle

Clases

TypeScript
Puzzle

Proyecto validación de tipado

TypeScript
Proyecto

Clases y objetos

TypeScript
Código

Encapsulación

TypeScript
Test

Herencia

TypeScript
Test

Proyecto sistema de votación

TypeScript
Proyecto

Reto genéricos con clases

TypeScript
Código

Inmutabilidad

TypeScript
Puzzle

Interfaces

TypeScript
Test

Funciones de alto orden

TypeScript
Test

Reto map y filter

TypeScript
Código

Control de flujo

TypeScript
Test

Interfaces

TypeScript
Código

Reto funciones orden superior

TypeScript
Código

Herencia y clases abstractas

TypeScript
Código

Reto tipos mapped

TypeScript
Código

Herencia de clases

TypeScript
Código

Reto funciones puras

TypeScript
Código

Variables y constantes

TypeScript
Puzzle

Introducción a TypeScript

TypeScript
Test

Reto testing unitario

TypeScript
Código

Funciones de primera clase

TypeScript
Puzzle

Clases

TypeScript
Test

OOP y CRUD en TypeScript

TypeScript
Proyecto

Interfaces y su implementación

TypeScript
Código

Tipos genéricos

TypeScript
Test

Namespaces

TypeScript
Test

Operadores y expresiones

TypeScript
Código

Proyecto generador de contraseñas

TypeScript
Proyecto

Reto unión e intersección

TypeScript
Código

Encapsulación

TypeScript
Puzzle

Tipos de unión e intersección

TypeScript
Test

Tipos de unión e intersección

TypeScript
Puzzle

Reto hola mundo en TS

TypeScript
Código

Variables y constantes

TypeScript
Código

Funciones puras

TypeScript
Test

Control de flujo

TypeScript
Código

Introducción a TypeScript

TypeScript
Código

Resolución de módulos

TypeScript
Test

Control de flujo

TypeScript
Puzzle

Reto tipos de utilidad

TypeScript
Código

Reto tipos literales y condicionales

TypeScript
Código

Reto exportar e importar

TypeScript
Código

Propiedades y métodos

TypeScript
Código

Tipos de utilidad

TypeScript
Test

Clases y objetos

TypeScript
Código

Tipos de datos, variables y constantes

TypeScript
Código

Proyecto Minigestor de tareas

TypeScript
Proyecto

Operadores

TypeScript
Puzzle

Funciones flecha y contexto

TypeScript
Código

Proyecto Inventario de productos

TypeScript
Proyecto

Funciones

TypeScript
Puzzle

Reto type aliases

TypeScript
Código

Funciones de alto orden

TypeScript
Puzzle

Funciones y parámetros tipados

TypeScript
Código

Tipos literales

TypeScript
Puzzle

Reto enums

TypeScript
Código

Tipos de utilidad

TypeScript
Puzzle

Modificadores de acceso y encapsulación

TypeScript
Código

Polimorfismo

TypeScript
Puzzle

Tipos genéricos

TypeScript
Puzzle

Reto módulos

TypeScript
Código

Tipos literales

TypeScript
Test

Inmutabilidad

TypeScript
Test

Proyecto Generator de datos

TypeScript
Proyecto

Variables y constantes

TypeScript
Test

Funciones de primera clase

TypeScript
Test

Todas las lecciones de TypeScript

Accede a todas las lecciones de TypeScript y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Introducción A Typescript

TypeScript

Introducción Y Entorno

Instalación Y Configuración De Typescript

TypeScript

Introducción Y Entorno

Tipos De Datos, Variables Y Constantes

TypeScript

Sintaxis

Operadores Y Expresiones

TypeScript

Sintaxis

Control De Flujo

TypeScript

Sintaxis

Funciones Y Parámetros Tipados

TypeScript

Sintaxis

Funciones Flecha Y Contexto

TypeScript

Sintaxis

Enums

TypeScript

Sintaxis

Type Aliases Y Aserciones De Tipo

TypeScript

Sintaxis

Clases Y Objetos

TypeScript

Programación Orientada A Objetos

Interfaces Y Su Implementación

TypeScript

Programación Orientada A Objetos

Modificadores De Acceso Y Encapsulación

TypeScript

Programación Orientada A Objetos

Herencia Y Clases Abstractas

TypeScript

Programación Orientada A Objetos

Polimorfismo

TypeScript

Programación Orientada A Objetos

Decoradores Básicos

TypeScript

Programación Orientada A Objetos

Propiedades Y Métodos

TypeScript

Programación Orientada A Objetos

Inmutabilidad

TypeScript

Programación Funcional

Funciones Puras Y Efectos Secundarios

TypeScript

Programación Funcional

Funciones De Primera Clase

TypeScript

Programación Funcional

Funciones De Alto Orden

TypeScript

Programación Funcional

Conceptos Básicos E Inmutabilidad

TypeScript

Programación Funcional

Funciones De Primera Clase Y Orden Superior

TypeScript

Programación Funcional

Composición De Funciones

TypeScript

Programación Funcional

Métodos Funcionales De Arrays (Map, Filter, Reduce)

TypeScript

Programación Funcional

Tipos Literales Y Tipos Condicionales

TypeScript

Tipos Intermedios Y Avanzados

Tipos Genéricos Básicos

TypeScript

Tipos Intermedios Y Avanzados

Tipos De Unión E Intersección

TypeScript

Tipos Intermedios Y Avanzados

Tipos De Utilidad (Partial, Required, Pick, Etc)

TypeScript

Tipos Intermedios Y Avanzados

Unknown, Never Y Tipos Especiales

TypeScript

Tipos Intermedios Y Avanzados

Tipos Mapped

TypeScript

Tipos Intermedios Y Avanzados

Genéricos Con Clases E Interfaces

TypeScript

Tipos Intermedios Y Avanzados

Módulos

TypeScript

Namespaces Y Módulos

Namespaces

TypeScript

Namespaces Y Módulos

Resolución De Módulos

TypeScript

Namespaces Y Módulos

Exportación E Importación De Módulos

TypeScript

Namespaces Y Módulos

Introducción A Módulos

TypeScript

Namespaces Y Módulos

Testing Unitario En Typescript

TypeScript

Testing

Accede GRATIS a TypeScript y certifícate

En esta lección

Objetivos de aprendizaje de esta lección

  • Comprender la importancia del testing como complemento al sistema de tipos de TypeScript para detectar errores que el compilador no puede identificar
  • Aprender a configurar un entorno de pruebas con Jest para proyectos TypeScript
  • Dominar la estructura básica de tests unitarios siguiendo el patrón AAA (Arrange-Act-Assert)
  • Implementar tests bien tipados aprovechando las interfaces y tipos de TypeScript para mejorar la seguridad y claridad
  • Aplicar buenas prácticas de testing como independencia, determinismo y cobertura adecuada para mantener la calidad del código