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.

  1. Instalación de Dependencias: Asegúrate de tener las dependencias necesarias instaladas. Angular CLI ya incluye Jasmine y Karma por defecto.

  2. Estructura de Archivos de Pruebas: Las pruebas unitarias se colocan en archivos con la extensión .spec.ts. Por ejemplo, si tienes un servicio llamado data.service.ts, su archivo de prueba sería data.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

  1. 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.
  2. 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

Módulo 2: Conceptos Básicos de TypeScript

Módulo 3: Componentes y Plantillas

Módulo 4: Directivas y Pipes

Módulo 5: Servicios e Inyección de Dependencias

Módulo 6: Enrutamiento y Navegación

Módulo 7: Formularios en Angular

Módulo 8: Cliente HTTP y Observables

Módulo 9: Gestión de Estado

Módulo 10: Pruebas en Angular

Módulo 11: Temas Avanzados

Módulo 12: Despliegue y Mejores Prácticas

© Copyright 2024. Todos los derechos reservados