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
getData
devuelve 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