En este tema, aprenderemos cómo realizar pruebas unitarias en los servicios de Angular. Los servicios son una parte fundamental de cualquier aplicación Angular, ya que encapsulan la lógica de negocio y permiten la comunicación con APIs externas. Probar estos servicios es crucial para asegurar que nuestra aplicación funcione correctamente y sea mantenible.
Contenido
Introducción a las Pruebas de Servicios
Las pruebas unitarias de servicios en Angular se centran en verificar que los métodos del servicio funcionen como se espera. Esto incluye:
- Verificar que los métodos devuelvan los valores correctos.
- Asegurarse de que las dependencias del servicio se comporten correctamente.
- Comprobar que los métodos manejen los errores adecuadamente.
Configuración del Entorno de Pruebas
Angular CLI proporciona un entorno de pruebas listo para usar con Jasmine y Karma. Para comenzar, asegúrate de que tu proyecto Angular esté configurado correctamente para ejecutar pruebas unitarias.
-
Instalación de Dependencias: Asegúrate de tener las dependencias necesarias instaladas. Angular CLI ya incluye Jasmine y Karma por defecto.
-
Estructura de Archivos de Pruebas: Las pruebas unitarias se colocan en archivos con la extensión
.spec.ts. Por ejemplo, si tienes un servicio llamadodata.service.ts, su archivo de prueba seríadata.service.spec.ts.
Creación de un Servicio de Ejemplo
Primero, crearemos un servicio simple que utilizaremos para nuestras pruebas.
// src/app/services/data.service.ts
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class DataService {
constructor() { }
getData(): string {
return 'Hello, World!';
}
}Escribiendo Pruebas Unitarias para Servicios
Ahora, escribiremos pruebas unitarias para el servicio DataService.
// src/app/services/data.service.spec.ts
import { TestBed } from '@angular/core/testing';
import { DataService } from './data.service';
describe('DataService', () => {
let service: DataService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(DataService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
it('should return "Hello, World!" from getData', () => {
expect(service.getData()).toBe('Hello, World!');
});
});Explicación del Código
- TestBed: Es el entorno de pruebas de Angular que nos permite configurar y crear instancias de los componentes y servicios que queremos probar.
- beforeEach: Se ejecuta antes de cada prueba individual. Aquí configuramos el módulo de pruebas y obtenemos una instancia del servicio.
- it: Define una prueba individual. En este caso, tenemos dos pruebas: una para verificar que el servicio se crea correctamente y otra para verificar que el método
getDatadevuelve el valor esperado.
Pruebas de Servicios con Dependencias
A menudo, los servicios dependen de otros servicios. Veamos cómo podemos probar un servicio que tiene dependencias.
Servicio con Dependencia
// src/app/services/message.service.ts
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class MessageService {
constructor() { }
getMessage(): string {
return 'Hello from MessageService!';
}
}
// src/app/services/advanced-data.service.ts
import { Injectable } from '@angular/core';
import { MessageService } from './message.service';
@Injectable({
providedIn: 'root'
})
export class AdvancedDataService {
constructor(private messageService: MessageService) { }
getAdvancedData(): string {
return this.messageService.getMessage();
}
}Pruebas del Servicio con Dependencia
// src/app/services/advanced-data.service.spec.ts
import { TestBed } from '@angular/core/testing';
import { AdvancedDataService } from './advanced-data.service';
import { MessageService } from './message.service';
describe('AdvancedDataService', () => {
let service: AdvancedDataService;
let messageServiceSpy: jasmine.SpyObj<MessageService>;
beforeEach(() => {
const spy = jasmine.createSpyObj('MessageService', ['getMessage']);
TestBed.configureTestingModule({
providers: [
AdvancedDataService,
{ provide: MessageService, useValue: spy }
]
});
service = TestBed.inject(AdvancedDataService);
messageServiceSpy = TestBed.inject(MessageService) as jasmine.SpyObj<MessageService>;
});
it('should be created', () => {
expect(service).toBeTruthy();
});
it('should return message from MessageService', () => {
const expectedMessage = 'Hello from MessageService!';
messageServiceSpy.getMessage.and.returnValue(expectedMessage);
expect(service.getAdvancedData()).toBe(expectedMessage);
});
});Explicación del Código
- jasmine.createSpyObj: Crea un objeto espía que simula el comportamiento del
MessageService. - providers: Configura el módulo de pruebas para usar el objeto espía en lugar del servicio real.
- messageServiceSpy.getMessage.and.returnValue: Configura el espía para que devuelva un valor específico cuando se llame al método
getMessage.
Ejercicios Prácticos
- Ejercicio 1: Crea un servicio que tenga un método para sumar dos números. Escribe pruebas unitarias para verificar que el método funciona correctamente.
- Ejercicio 2: Crea un servicio que dependa de otro servicio que devuelva un mensaje. Escribe pruebas unitarias para verificar que el servicio principal devuelve el mensaje correcto.
Soluciones
Ejercicio 1
// src/app/services/math.service.ts
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class MathService {
constructor() { }
add(a: number, b: number): number {
return a + b;
}
}
// src/app/services/math.service.spec.ts
import { TestBed } from '@angular/core/testing';
import { MathService } from './math.service';
describe('MathService', () => {
let service: MathService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(MathService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
it('should add two numbers', () => {
expect(service.add(1, 2)).toBe(3);
});
});Ejercicio 2
// src/app/services/message.service.ts
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class MessageService {
constructor() { }
getMessage(): string {
return 'Hello from MessageService!';
}
}
// src/app/services/complex.service.ts
import { Injectable } from '@angular/core';
import { MessageService } from './message.service';
@Injectable({
providedIn: 'root'
})
export class ComplexService {
constructor(private messageService: MessageService) { }
getComplexMessage(): string {
return this.messageService.getMessage();
}
}
// src/app/services/complex.service.spec.ts
import { TestBed } from '@angular/core/testing';
import { ComplexService } from './complex.service';
import { MessageService } from './message.service';
describe('ComplexService', () => {
let service: ComplexService;
let messageServiceSpy: jasmine.SpyObj<MessageService>;
beforeEach(() => {
const spy = jasmine.createSpyObj('MessageService', ['getMessage']);
TestBed.configureTestingModule({
providers: [
ComplexService,
{ provide: MessageService, useValue: spy }
]
});
service = TestBed.inject(ComplexService);
messageServiceSpy = TestBed.inject(MessageService) as jasmine.SpyObj<MessageService>;
});
it('should be created', () => {
expect(service).toBeTruthy();
});
it('should return message from MessageService', () => {
const expectedMessage = 'Hello from MessageService!';
messageServiceSpy.getMessage.and.returnValue(expectedMessage);
expect(service.getComplexMessage()).toBe(expectedMessage);
});
});Conclusión
En esta sección, hemos aprendido cómo realizar pruebas unitarias en los servicios de Angular. Hemos cubierto la configuración del entorno de pruebas, la creación de servicios de ejemplo, y cómo escribir pruebas unitarias tanto para servicios simples como para servicios con dependencias. Las pruebas unitarias son una herramienta poderosa para asegurar la calidad y mantenibilidad de tu código. En el siguiente tema, exploraremos las pruebas de extremo a extremo en Angular.
Curso de Angular 2+
Módulo 1: Introducción a Angular
- ¿Qué es Angular?
- Configuración del Entorno de Desarrollo
- Tu Primera Aplicación Angular
- Arquitectura de Angular
Módulo 2: Conceptos Básicos de TypeScript
- Introducción a TypeScript
- Variables y Tipos de Datos en TypeScript
- Funciones y Funciones Flecha
- Clases e Interfaces
Módulo 3: Componentes y Plantillas
- Creación de Componentes
- Plantillas de Componentes
- Estilos de Componentes
- Interacción entre Componentes
Módulo 4: Directivas y Pipes
Módulo 5: Servicios e Inyección de Dependencias
- Introducción a los Servicios
- Creación y Uso de Servicios
- Inyección de Dependencias
- Inyectores Jerárquicos
Módulo 6: Enrutamiento y Navegación
Módulo 7: Formularios en Angular
- Formularios Basados en Plantillas
- Formularios Reactivos
- Validación de Formularios
- Formularios Dinámicos
Módulo 8: Cliente HTTP y Observables
- Introducción al Cliente HTTP
- Realizando Solicitudes HTTP
- Manejo de Respuestas HTTP
- Uso de Observables
Módulo 9: Gestión de Estado
- Introducción a la Gestión de Estado
- Uso de Servicios para la Gestión de Estado
- NgRx Store
- NgRx Effects
