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ícateImportancia 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
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.
- 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 aliastest
) 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'
};
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
Reto composición de funciones
Reto tipos especiales
Reto tipos genéricos
Módulos
Polimorfismo
Funciones TypeScript
Interfaces
Funciones puras
Reto namespaces
Funciones flecha
Polimorfismo
Operadores
Conversor de unidades
Funciones flecha
Control de flujo
Herencia
Clases
Proyecto validación de tipado
Clases y objetos
Encapsulación
Herencia
Proyecto sistema de votación
Reto genéricos con clases
Inmutabilidad
Interfaces
Funciones de alto orden
Reto map y filter
Control de flujo
Interfaces
Reto funciones orden superior
Herencia y clases abstractas
Reto tipos mapped
Herencia de clases
Reto funciones puras
Variables y constantes
Introducción a TypeScript
Reto testing unitario
Funciones de primera clase
Clases
OOP y CRUD en TypeScript
Interfaces y su implementación
Tipos genéricos
Namespaces
Operadores y expresiones
Proyecto generador de contraseñas
Reto unión e intersección
Encapsulación
Tipos de unión e intersección
Tipos de unión e intersección
Reto hola mundo en TS
Variables y constantes
Funciones puras
Control de flujo
Introducción a TypeScript
Resolución de módulos
Control de flujo
Reto tipos de utilidad
Reto tipos literales y condicionales
Reto exportar e importar
Propiedades y métodos
Tipos de utilidad
Clases y objetos
Tipos de datos, variables y constantes
Proyecto Minigestor de tareas
Operadores
Funciones flecha y contexto
Proyecto Inventario de productos
Funciones
Reto type aliases
Funciones de alto orden
Funciones y parámetros tipados
Tipos literales
Reto enums
Tipos de utilidad
Modificadores de acceso y encapsulación
Polimorfismo
Tipos genéricos
Reto módulos
Tipos literales
Inmutabilidad
Proyecto Generator de datos
Variables y constantes
Funciones de primera clase
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
Introducción Y Entorno
Instalación Y Configuración De Typescript
Introducción Y Entorno
Tipos De Datos, Variables Y Constantes
Sintaxis
Operadores Y Expresiones
Sintaxis
Control De Flujo
Sintaxis
Funciones Y Parámetros Tipados
Sintaxis
Funciones Flecha Y Contexto
Sintaxis
Enums
Sintaxis
Type Aliases Y Aserciones De Tipo
Sintaxis
Clases Y Objetos
Programación Orientada A Objetos
Interfaces Y Su Implementación
Programación Orientada A Objetos
Modificadores De Acceso Y Encapsulación
Programación Orientada A Objetos
Herencia Y Clases Abstractas
Programación Orientada A Objetos
Polimorfismo
Programación Orientada A Objetos
Decoradores Básicos
Programación Orientada A Objetos
Propiedades Y Métodos
Programación Orientada A Objetos
Inmutabilidad
Programación Funcional
Funciones Puras Y Efectos Secundarios
Programación Funcional
Funciones De Primera Clase
Programación Funcional
Funciones De Alto Orden
Programación Funcional
Conceptos Básicos E Inmutabilidad
Programación Funcional
Funciones De Primera Clase Y Orden Superior
Programación Funcional
Composición De Funciones
Programación Funcional
Métodos Funcionales De Arrays (Map, Filter, Reduce)
Programación Funcional
Tipos Literales Y Tipos Condicionales
Tipos Intermedios Y Avanzados
Tipos Genéricos Básicos
Tipos Intermedios Y Avanzados
Tipos De Unión E Intersección
Tipos Intermedios Y Avanzados
Tipos De Utilidad (Partial, Required, Pick, Etc)
Tipos Intermedios Y Avanzados
Unknown, Never Y Tipos Especiales
Tipos Intermedios Y Avanzados
Tipos Mapped
Tipos Intermedios Y Avanzados
Genéricos Con Clases E Interfaces
Tipos Intermedios Y Avanzados
Módulos
Namespaces Y Módulos
Namespaces
Namespaces Y Módulos
Resolución De Módulos
Namespaces Y Módulos
Exportación E Importación De Módulos
Namespaces Y Módulos
Introducción A Módulos
Namespaces Y Módulos
Testing Unitario En Typescript
Testing
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