Tres maneras de inyectar servicios en unit tests con Angular
Feb 26, 2022

Tres maneras de inyectar servicios en unit tests con Angular

🌟 Tres maneras de inyectar servicios en unit tests con Angular 🌟

Cuando escribimos tests unitarios para nuestros servicios en Angular, tenemos varias opciones para inyectar el servicio en cada uno de nuestros bloques "it". En este post, exploraremos algunas ventajas y desventajas de cada una de las maneras. 💡

📚 Un vistazo al servicio que deseamos probar

Revisemos un servicio súper simple que deseamos probar; este servicio solo tendrá una propiedad y un método para probar:

// Importa el decorador Injectable desde el paquete @angular/core
import { Injectable } from '@angular/core';

// Define una clase llamada "Observable" (aunque parece que no es utilizada y podría eliminarse)
class Observable {
}

// Utiliza el decorador Injectable para indicar que la clase NamesService puede ser inyectada en otras partes de la aplicación
@Injectable({
    providedIn: 'root' // Especifica que esta clase debe proporcionarse en el nivel raíz (root) del módulo de inyección de dependencias
})
// Exporta la clase NamesService para que pueda ser importada y utilizada en otras partes de la aplicación
export class NamesService {

    // Declara una propiedad privada "names" que es un array de strings e inicializa con dos nombres: 'Juan' y 'Mati'
    private names: string[] = ['Juan', 'Mati'];

    // Define el constructor de la clase NamesService (está vacío en este caso)
    constructor() { }

    // Define un método público llamado "getNames" que retorna el array de nombres
    public getNames() {
        return this.names;
    }
}
📝 Forma 1: Inyectar el servicio directamente en cada bloque "it"

Por defecto, nuestro archivo de pruebas asociado nos propone inyectar el servicio directamente en cada bloque "it" de esta manera:

// Importa TestBed e inject desde el paquete @angular/core/testing, necesarios para realizar pruebas en Angular
import { TestBed, inject } from '@angular/core/testing';
// Importa la clase NamesService desde el archivo './names.service'
import { NamesService } from './names.service';

// Define el conjunto de pruebas para la clase NamesService
describe('NamesService', () => {
    // Antes de cada prueba, configura el módulo de pruebas con TestBed
    beforeEach(() => {
        TestBed.configureTestingModule({
            providers: [NamesService] // Registra NamesService como un proveedor de servicios en el módulo de pruebas
        });
    });

    // Define una prueba que verifica si la instancia del servicio NamesService se crea correctamente
    it('should be created', inject([NamesService], (service: NamesService) => {
        // El código de la prueba iría aquí
    }));

    // Define una prueba personalizada (parece incompleta y requiere más detalles sobre lo que se espera probar)
    it('should be something', inject([NamesService], (service: NamesService) => {
        // El código de la prueba iría aquí
    }));

    // Define otra prueba personalizada (también parece incompleta y requiere más detalles sobre lo que se espera probar)
    it('should be .....', inject([NamesService], (service: NamesService) => {
        // El código de la prueba iría aquí
    }));
});

🚀 Ventaja: Empezamos con un servicio nuevo y limpio en cada bloque "it".

🚧 Desventaja: Requiere repetir la misma línea todo el tiempo.

📝 Forma 2: Usar Angular TestBed para evitar repetir la línea

Podemos valernos de Angular TestBed para evitar repetir esta línea. Algo así:

// Antes de cada prueba, ejecuta la siguiente función anónima
beforeEach(() => {
  // Configura el módulo de pruebas con TestBed
  TestBed.configureTestingModule({
    providers: [NamesService], // Registra NamesService como un proveedor de servicios en el módulo de pruebas
  });

  // Es otra forma de poner a disposición el servicio durante las pruebas
  // Obtiene una instancia de NamesService utilizando TestBed.get()
  service = TestBed.get(NamesService);
});

// Define una prueba (se omite parte del código) ...
it("should be created", () => {
  // El servicio está perfectamente disponible
  // El código de la prueba para verificar si la instancia del servicio se crea correctamente iría aquí
});

🚀 Ventaja: Cada bloque "it" tiene una versión nueva del servicio, evitando empezar con un servicio corrupto por tests anteriores.

🚧 Desventaja: No permite compartir la misma instancia del servicio entre diferentes bloques "it".

📝 Forma 3: Persistir el estado del servicio a través de los diferentes "it"

Para tener exactamente la misma instancia del servicio a través de los bloques "it", podemos usar el siguiente enfoque:

// Antes de todas las pruebas, ejecuta la siguiente función anónima
beforeAll(() => {
  // Obtiene una instancia de NamesService utilizando TestBed.get()
  service = TestBed.get(NamesService);
});

// Define una prueba que verifica si el valor de la propiedad myVar se establece en 13
it("should be myVar set to 13", () => {
  // Llama al método setMyVar() del servicio con el argumento 13
  service.setMyVar(13);
  // Espera que el valor de la propiedad myVar del servicio sea igual a 13
  expect(service.myVar).toBe(13);
});

// Define una prueba que verifica si el valor de la propiedad myVar todavía está establecido en 13 y luego se establece en 12
it("should be myVar still set to 13 and then set to 12", () => {
  // Espera que el valor de la propiedad myVar del servicio sea igual a 13
  expect(service.myVar).toBe(13);
  // Llama al método setMyVar() del servicio con el argumento 12
  service.setMyVar(12);
  // Espera que el valor de la propiedad myVar del servicio sea igual a 12
  expect(service.myVar).toBe(12);
});

🚀 Ventaja: Permite persistir el estado del servicio a través de los diferentes "it", lo cual puede ser útil en pruebas unitarias relacionadas con procesos de CRUD para evitar repetir código y tener la trazabilidad de todo el test.

🚧 Desventaja: Puede hacer que los tests sean más difíciles de mantener si no se maneja adecuadamente la persistencia del estado.

🎓 Conclusiones

Repasando las tres estrategias para inyectar servicios en nuestros tests, tenemos:

  1. inject([NamesService], (service: NamesService) => {
  2. beforeEach(() => service = TestBed.get(NamesService));
  3. beforeAll(() => service = TestBed.get(NamesService));
💪 Ejercicios para practicar
  1. Crea un servicio simple que maneje una lista de tareas pendientes (ToDo) y utiliza las tres formas de inyectar servicios en los tests para probar diferentes casos.
  2. Compara los tres enfoques en un escenario donde se requiera probar un servicio con persistencia de estado entre bloques "it" y otro que no lo requiera. ¿Cuál enfoque te parece más adecuado para cada caso?
  3. Investiga cómo manejar la persistencia de estado en los tests de forma adecuada para evitar problemas de mantenimiento.
📚 Resumen en 3 puntos
  1. Existen tres formas principales de inyectar servicios en unit tests con Angular, cada una con sus ventajas y desventajas.
  2. La elección del enfoque adecuado dependerá de si se requiere una nueva instancia del servicio en cada bloque "it" o si se necesita persistir el estado del servicio a lo largo de los tests.
  3. Practicar con diferentes escenarios ayudará a determinar cuál enfoque es el más adecuado para cada situación.

Espero que este post te sea de utilidad. Si tienes alguna duda, no dudes en dejarme un comentario en la parte de abajo. Recuerda que si te gustó, también puedes compartir usando los links a las redes sociales en la parte de abajo. 😄

Sebastian Gomez

Sebastian Gomez

Creador de contenido principalmente acerca de tecnología.

Leave a Reply

0 Comments

Related Posts

Categorias