Configuración de tests con TestBed
El testing en Angular es fundamental para garantizar que nuestras aplicaciones funcionen correctamente y mantengan su calidad a lo largo del tiempo. Angular proporciona un ecosistema robusto de testing que nos permite verificar tanto la lógica de negocio como la integración entre componentes y servicios.
Introducción al testing en Angular
Angular viene configurado por defecto con herramientas de testing modernas. En Angular 20+, el framework ha evolucionado hacia el uso de Vitest como runner principal, reemplazando gradualmente a Karma, aunque los conceptos fundamentales de testing permanecen universales y aplicables a cualquier herramienta.
Los tests en Angular se organizan en archivos con extensión .spec.ts
y siguen el patrón AAA (Arrange, Act, Assert):
- Arrange: Configuramos el entorno de testing
- Act: Ejecutamos la funcionalidad a testear
- Assert: Verificamos que el resultado sea el esperado
¿Qué es TestBed?
TestBed es la utilidad principal de Angular para configurar y crear un módulo de testing dinámico. Actúa como un mini-framework que simula el entorno de ejecución de Angular, permitiéndonos instanciar servicios, componentes y otros elementos de forma aislada para testing.
TestBed nos proporciona:
- Configuración declarativa del entorno de testing
- Inyección de dependencias controlada
- Aislamiento de cada test
- Simulación del contexto de ejecución de Angular
Estructura básica de un test de servicio
Veamos la estructura fundamental de un test de servicio en Angular:
import { TestBed } from '@angular/core/testing';
import { MiServicio } from './mi-servicio.service';
describe('MiServicio', () => {
let service: MiServicio;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(MiServicio);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
Configuración con TestBed.configureTestingModule()
El método configureTestingModule() es donde definimos la configuración específica para nuestros tests. Acepta un objeto de configuración similar al que usaríamos en un módulo de Angular tradicional:
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule, RouterTestingModule],
providers: [
MiServicio,
{ provide: ConfigService, useValue: mockConfig }
]
});
service = TestBed.inject(MiServicio);
});
Los parámetros más comunes de configuración incluyen:
- imports: Módulos necesarios para el test
- providers: Servicios y sus configuraciones
- declarations: Componentes, directivas y pipes (cuando sea necesario)
Inyección de servicios con TestBed.inject()
Una vez configurado el TestBed, utilizamos TestBed.inject() para obtener instancias de los servicios que queremos testear. Este método reemplaza al antiguo TestBed.get()
y proporciona mejor tipado:
// Inyección básica
const miServicio = TestBed.inject(MiServicio);
// Inyección con token
const httpClient = TestBed.inject(HttpClient);
// Inyección de servicios con interfaces
const logger = TestBed.inject(LoggerService);
Configuración para servicios con dependencias
Cuando nuestros servicios tienen dependencias, necesitamos configurarlas en el TestBed. Esto es especialmente importante para servicios que dependen de HttpClient u otros servicios externos:
import { HttpClientTestingModule } from '@angular/common/http/testing';
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [DataService]
});
service = TestBed.inject(DataService);
});
Configuración para servicios standalone
Con la arquitectura moderna de Angular 20+, muchos servicios se definen con providedIn: 'root'. Para estos servicios, la configuración es más simple:
// Servicio con providedIn: 'root'
@Injectable({ providedIn: 'root' })
export class UserService {
constructor(private http: HttpClient) {}
}
// Test configuration
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule]
});
service = TestBed.inject(UserService);
});
Limpieza entre tests
Angular automáticamente limpia el TestBed entre cada test, pero es una buena práctica ser explícito sobre la configuración. Cada beforeEach() recrea el entorno de testing de forma aislada:
describe('UserService', () => {
let service: UserService;
beforeEach(() => {
// Configuración limpia para cada test
TestBed.configureTestingModule({
imports: [HttpClientTestingModule]
});
service = TestBed.inject(UserService);
});
// Cada test comienza con un entorno limpio
it('should initialize with default values', () => {
expect(service).toBeTruthy();
});
});
Configuración para testing de signals
Dado que trabajamos con Angular 20+ y signals, nuestros servicios pueden usar signals para gestionar estado. La configuración del TestBed permanece igual, ya que los signals funcionan nativamente:
@Injectable({ providedIn: 'root' })
export class StateService {
private _count = signal(0);
count = this._count.asReadonly();
increment() {
this._count.update(current => current + 1);
}
}
// La configuración es idéntica
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(StateService);
});
Esta configuración básica con TestBed nos proporciona la base sólida necesaria para escribir tests efectivos de servicios en Angular, estableciendo el entorno aislado y controlado que necesitamos para verificar el comportamiento de nuestro código.
Testing básico de métodos y HTTP calls
Una vez que tenemos configurado nuestro entorno de testing con TestBed, podemos proceder a testear la funcionalidad real de nuestros servicios. Esto incluye verificar que los métodos funcionen correctamente y que las llamadas HTTP se comporten como esperamos.
Testing de métodos básicos
Los métodos básicos de un servicio son aquellos que no requieren dependencias externas y realizan operaciones síncronas. Estos son ideales para comenzar con el testing:
@Injectable({ providedIn: 'root' })
export class CalculatorService {
add(a: number, b: number): number {
return a + b;
}
multiply(a: number, b: number): number {
return a * b;
}
isEven(num: number): boolean {
return num % 2 === 0;
}
}
Los tests para estos métodos son directos y utilizan assertions básicas:
describe('CalculatorService', () => {
let service: CalculatorService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(CalculatorService);
});
it('should add two numbers correctly', () => {
const result = service.add(3, 5);
expect(result).toBe(8);
});
it('should multiply two numbers correctly', () => {
const result = service.multiply(4, 6);
expect(result).toBe(24);
});
it('should identify even numbers', () => {
expect(service.isEven(4)).toBe(true);
expect(service.isEven(7)).toBe(false);
});
});
Testing de servicios con estado usando signals
Para servicios que manejan estado con signals, verificamos tanto la lectura como la modificación del estado:
@Injectable({ providedIn: 'root' })
export class CounterService {
private _count = signal(0);
count = this._count.asReadonly();
increment(): void {
this._count.update(current => current + 1);
}
reset(): void {
this._count.set(0);
}
setCount(value: number): void {
this._count.set(value);
}
}
Los tests verifican el comportamiento reactivo de los signals:
it('should start with count of 0', () => {
expect(service.count()).toBe(0);
});
it('should increment count by 1', () => {
service.increment();
expect(service.count()).toBe(1);
service.increment();
expect(service.count()).toBe(2);
});
it('should reset count to 0', () => {
service.setCount(5);
service.reset();
expect(service.count()).toBe(0);
});
it('should set count to specific value', () => {
service.setCount(10);
expect(service.count()).toBe(10);
});
Introducción al testing de HTTP calls
Cuando nuestros servicios realizan llamadas HTTP, necesitamos una forma de interceptar y simular estas llamadas durante los tests. Angular proporciona HttpTestingController para este propósito, que nos permite controlar las peticiones HTTP sin realizar llamadas reales a un servidor.
Primero, veamos un servicio básico que hace llamadas HTTP:
@Injectable({ providedIn: 'root' })
export class UserService {
private apiUrl = 'https://api.example.com/users';
constructor(private http: HttpClient) {}
getUsers(): Observable<User[]> {
return this.http.get<User[]>(this.apiUrl);
}
getUserById(id: number): Observable<User> {
return this.http.get<User>(`${this.apiUrl}/${id}`);
}
createUser(user: Partial<User>): Observable<User> {
return this.http.post<User>(this.apiUrl, user);
}
}
Configuración para testing HTTP
Para testear servicios con HTTP, necesitamos importar HttpClientTestingModule y usar HttpTestingController:
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
describe('UserService', () => {
let service: UserService;
let httpTestingController: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule]
});
service = TestBed.inject(UserService);
httpTestingController = TestBed.inject(HttpTestingController);
});
afterEach(() => {
// Verificar que no hay requests pendientes
httpTestingController.verify();
});
});
Testing básico de GET requests
Para testear una petición GET, utilizamos el patrón de suscribirnos al Observable y verificar tanto la petición como la respuesta:
it('should get users list', () => {
const mockUsers: User[] = [
{ id: 1, name: 'Juan', email: 'juan@example.com' },
{ id: 2, name: 'Ana', email: 'ana@example.com' }
];
service.getUsers().subscribe(users => {
expect(users).toEqual(mockUsers);
expect(users.length).toBe(2);
});
// Interceptar la petición HTTP
const req = httpTestingController.expectOne('https://api.example.com/users');
// Verificar que es una petición GET
expect(req.request.method).toBe('GET');
// Simular la respuesta del servidor
req.flush(mockUsers);
});
Testing de GET con parámetros
Para peticiones que incluyen parámetros en la URL:
it('should get user by id', () => {
const mockUser: User = { id: 1, name: 'Juan', email: 'juan@example.com' };
service.getUserById(1).subscribe(user => {
expect(user).toEqual(mockUser);
expect(user.id).toBe(1);
});
const req = httpTestingController.expectOne('https://api.example.com/users/1');
expect(req.request.method).toBe('GET');
req.flush(mockUser);
});
Testing básico de POST requests
Para testear peticiones POST, verificamos tanto el cuerpo de la petición como la respuesta:
it('should create new user', () => {
const newUser: Partial<User> = { name: 'Carlos', email: 'carlos@example.com' };
const createdUser: User = { id: 3, name: 'Carlos', email: 'carlos@example.com' };
service.createUser(newUser).subscribe(user => {
expect(user).toEqual(createdUser);
expect(user.id).toBeDefined();
});
const req = httpTestingController.expectOne('https://api.example.com/users');
expect(req.request.method).toBe('POST');
expect(req.request.body).toEqual(newUser);
req.flush(createdUser);
});
Assertions básicas con expect()
Las assertions son la parte fundamental de nuestros tests. Utilizamos diferentes matchers de Jasmine según lo que queremos verificar:
- toBe(): Para comparación exacta (
===
) - toEqual(): Para comparación profunda de objetos
- toBeTruthy() / toBeFalsy(): Para verificar valores verdaderos o falsos
- toContain(): Para verificar que un array contiene un elemento
- toBeDefined() / toBeUndefined(): Para verificar si algo está definido
it('should demonstrate basic assertions', () => {
const user = { id: 1, name: 'Test User' };
const numbers = [1, 2, 3, 4, 5];
// Comparaciones exactas
expect(user.id).toBe(1);
expect(user.name).toBe('Test User');
// Comparaciones de objetos
expect(user).toEqual({ id: 1, name: 'Test User' });
// Arrays
expect(numbers).toContain(3);
expect(numbers.length).toBe(5);
// Valores definidos
expect(user.id).toBeDefined();
expect(user.age).toBeUndefined();
});
Manejo básico de errores HTTP
También podemos testear cómo nuestros servicios manejan errores HTTP:
it('should handle HTTP error', () => {
service.getUsers().subscribe({
next: () => fail('Expected error'),
error: (error) => {
expect(error.status).toBe(500);
}
});
const req = httpTestingController.expectOne('https://api.example.com/users');
// Simular un error del servidor
req.flush('Server Error', { status: 500, statusText: 'Internal Server Error' });
});
Este enfoque básico de testing nos proporciona las herramientas fundamentales para verificar que nuestros servicios funcionen correctamente, tanto en su lógica interna como en su comunicación con APIs externas, estableciendo una base sólida para escribir tests confiables y mantenibles.

Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Angular es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de Angular
Explora más contenido relacionado con Angular y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
- Comprender la configuración básica de tests en Angular con TestBed.
- Aprender a inyectar servicios y configurar dependencias para testing.
- Realizar tests de métodos síncronos y servicios con estado usando signals.
- Testear llamadas HTTP simulando peticiones con HttpTestingController.
- Manejar assertions básicas y pruebas de errores en servicios Angular.