Pruebas Unitarias Avanzadas en Angular: Dominando Jasmine, Karma y Jest para Aplicaciones Robusta
Este tutorial completo te guía a través de las técnicas más avanzadas para realizar pruebas unitarias en Angular. Descubre cómo configurar tu entorno, aislar componentes con mocks y spies, probar código asíncrono con RxJS y migrar a Jest para optimizar tus pruebas.
Introducción a las Pruebas Unitarias en Angular Intermedio
Las pruebas unitarias son un pilar fundamental en el desarrollo de software moderno. Nos permiten garantizar que cada pieza individual de nuestra aplicación funcione correctamente antes de integrarla en el sistema completo. En el ecosistema de Angular, el testing está integrado desde el primer día, pero a medida que las aplicaciones crecen, las necesidades de rendimiento, complejidad y asincronía aumentan exponencialmente.
En este tutorial avanzado, profundizaremos en las técnicas esenciales para escribir pruebas unitarias robustas, eficientes y mantenibles. Aprenderemos a manejar dependencias complejas, flujos reactivos con RxJS, peticiones HTTP y a optimizar nuestra suite de pruebas mediante el uso de herramientas modernas como Jest.
Configuración del Entorno y TestBed Avanzado
El corazón de las pruebas en Angular es el TestBed. Nos permite configurar y crear un módulo de pruebas virtual para nuestro componente o servicio. Cuando las aplicaciones crecen, configurar el TestBed repetitivamente puede volverse tedioso y lento.
Patrones de Configuración Reutilizables
Para evitar la duplicación de código en tus archivos .spec.ts, es una excelente práctica implementar funciones de configuración auxiliar.
import { TestBed } from '@angular/core/testing';
import { UserService } from './user.service';
import { HttpClientTestingModule } from '@angular/common/http/testing';
describe('UserService - Configuración Avanzada', () => {
let service: UserService;
const setup = (providers: any[] = []) => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [UserService, ...providers]
});
service = TestBed.inject(UserService);
};
it('debe crearse correctamente', () => {
setup();
expect(service).toBeTruthy();
});
});
Aislando Componentes: Mocks, Spies y Stubs
Cuando probamos un componente, rara vez este trabaja de forma aislada. Suele depender de servicios, otros componentes o rutas. Para asegurar que nuestra prueba sea verdaderamente unitaria, debemos aislar el sistema bajo prueba utilizando Mocks y Spies.
Creando Spies Profesionales con Jasmine
Un spy nos permite vigilar funciones, registrar llamadas y alterar su comportamiento de retorno sin necesidad de ejecutar la implementación real.
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { UserProfileComponent } from './user-profile.component';
import { AuthService } from '../auth.service';
import { of } from 'rxjs';
describe('UserProfileComponent', () => {
let component: UserProfileComponent;
let fixture: ComponentFixture<UserProfileComponent>;
let authServiceSpy: jasmine.SpyObj<AuthService>;
beforeEach(async () => {
const spy = jasmine.createSpyObj('AuthService', ['getUserProfile', 'updateProfile']);
await TestBed.configureTestingModule({
declarations: [UserProfileComponent],
providers: [{ provide: AuthService, useValue: spy }]
}).compileComponents();
authServiceSpy = TestBed.inject(AuthService) as jasmine.SpyObj<AuthService>;
fixture = TestBed.createComponent(UserProfileComponent);
component = fixture.componentInstance;
});
it('debe cargar el perfil del usuario al inicializarse', () => {
const mockUser = { id: 1, name: 'Ana Gómez', role: 'Admin' };
authServiceSpy.getUserProfile.and.returnValue(of(mockUser));
fixture.detectChanges(); // Dispara ngOnInit
expect(authServiceSpy.getUserProfile.calls.count()).toBe(1);
expect(component.user).toEqual(mockUser);
});
});
Pruebas de Código Asíncrono y RxJS
El manejo de la asincronía es uno de los mayores desafíos al testear aplicaciones Angular. Entre temporizadores (setTimeout), promesas y flujos reactivos de RxJS, nuestras pruebas pueden volverse inestables si no utilizamos las utilidades correctas.
Probando Observables con fakeAsync y tick
Cuando trabajamos con operadores temporales de RxJS como debounceTime, delay o throttleTime, fakeAsync nos permite controlar el tiempo de forma síncrona.
import { fakeAsync, tick } from '@angular/core/testing';
import { SearchService } from './search.service';
describe('SearchService - Operadores Asíncronos', () => {
let service: SearchService;
beforeEach(() => {
service = new SearchService();
});
it('debe aplicar debounce a las búsquedas', fakeAsync(() => {
const results: string[] = [];
service.searchResults$.forEach(res => results.push(res));
service.search('Angular');
tick(150);
expect(results.length).toBe(0);
tick(200); // Completa el debounce de 350ms total
expect(results.length).toBe(1);
expect(results[0]).toBe('Angular');
}));
});
Uso de waitForAsync para Operaciones DOM
Para componentes que realizan operaciones pesadas en el DOM o dependen de plantillas externas y hojas de estilo, es necesario utilizar waitForAsync para asegurar que la compilación asíncrona haya finalizado antes de ejecutar los assertions.
Migrando de Karma a Jest: Velocidad y Eficiencia
Tradicionalmente, Angular ha utilizado Karma y Jasmine para la ejecución de pruebas. Sin embargo, Jest se ha convertido en el estándar de la industria debido a su velocidad superior, soporte nativo para TypeScript (vía jest o analogjs), y capacidades avanzadas de mocking de instantáneas (snapshots).
Pasos Básicos para Configurar Jest en un Proyecto Angular
npm install jest jest-preset-angular @types/jest --save-devjest.config.js en la raíz del proyecto.tsconfig.spec.json para incluir los tipos de Jest.package.json apuntando a Jest.Buenas Prácticas y Cobertura de Código
Una suite de pruebas extensa no sirve de nada si el código que cubre es irrelevante. Debemos apuntar a una cobertura de código significativa, enfocada en la lógica de negocio y los casos límite (edge cases).
Métricas Recomendadas de Cobertura
| Métrica | Objetivo Mínimo | Descripción |
|---|---|---|
| --- | --- | --- |
| Sentencias (Statements) | 85% | Porcentaje de instrucciones ejecutadas |
| Ramas (Branches) | 80% | Cobertura de condiciones if/else y operadores ternarios |
| --- | --- | --- |
| Funciones (Functions) | 90% | Funciones y métodos probados |
| Líneas (Lines) | 85% | Líneas de código efectivas evaluadas |
Consejos Finales para Mantener un Código Testeable
Haz clic para ver las reglas de oro del Clean Code en Testing
- Mantén las pruebas independientes: Ninguna prueba debe depender del resultado o estado generado por otra.
- Usa nombres descriptivos: El nombre de la prueba debe explicar claramente el escenario y el resultado esperado (ej: 'debe emitir un evento de error cuando el servidor responda con código 500').
- Evita lógica compleja en los tests: Las pruebas deben ser lineales y fáciles de leer. No utilices bucles ni condiciones complejas dentro de tus bloques
it.
Conclusión
Dominar las pruebas unitarias avanzadas en Angular transforma radicalmente la calidad de tus proyectos. Al dominar el uso de TestBed, aislar componentes con mocks y spies, gestionar la asincronía con precisión y considerar la adopción de herramientas veloces como Jest, garantizas aplicaciones estables, escalables y listas para producción.
Sigue practicando, refactoriza tus pruebas existentes y haz del testing una parte indivisible de tu flujo de desarrollo diario.
Tutoriales relacionados
- ¡Crea Formularios Dinámicos y Reactivos con `FormArray` en Angular!intermediate20 min
- Optimización de Rendimiento en Aplicaciones Angular: Estrategias Avanzadas para una Experiencia Ultra Rápidaadvanced25 min
- Gestión de Estado Reactiva con NgRx en Angular: Una Guía Completaintermediate15 min
- Componentes de Contenido Reutilizables en Angular: ¡Crea Módulos Compartidos y Bibliotecas!intermediate20 min
- ¡Desbloquea la Personalización! 🎨 Creando Directivas Estructurales en Angularadvanced18 min
Comentarios (0)
Aún no hay comentarios. ¡Sé el primero!