Tres maneras de inyectar servicios en unit tests con Angular
Feb 26, 2022
Updated: Jun 24, 2026

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 las 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. Para que los tres ejemplos funcionen tal cual, el servicio expone una lista de nombres, una propiedad de estado myVar y un método para actualizarla:

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

// 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 se provee en el nivel raíz del inyector de dependencias
})
export class NamesService {
  // Lista privada de nombres inicializada con dos valores
  private names: string[] = ['Juan', 'Mati'];

  // Propiedad de estado que usaremos en la Forma 3 para demostrar la persistencia
  public myVar = 0;

  constructor() {}

  // Método público que retorna el array de nombres
  public getNames(): string[] {
    return this.names;
  }

  // Método público que actualiza el valor de myVar
  public setMyVar(value: number): void {
    this.myVar = value;
  }
}

Forma 1: Inyectar el servicio directamente en cada bloque it

Durante mucho tiempo, el archivo de pruebas que generaba el Angular CLI proponía inyectar el servicio directamente en cada bloque it usando el helper funcional inject(...) de @angular/core/testing, de esta manera:

// Importa TestBed e inject desde @angular/core/testing, necesarios para probar en Angular
import { TestBed, inject } from '@angular/core/testing';
// Importa la clase NamesService
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 proveedor en el módulo de pruebas
    });
  });

  // Verifica que la instancia del servicio se crea correctamente
  it('should be created', inject([NamesService], (service: NamesService) => {
    expect(service).toBeTruthy();
  }));

  it('should be something', inject([NamesService], (service: NamesService) => {
    // El código de la prueba iría aquí
  }));

  it('should be .....', inject([NamesService], (service: NamesService) => {
    // El código de la prueba iría aquí
  }));
});

Ten en cuenta que este es el patrón heredado. El helper inject([...], (service) => {...}) de @angular/core/testing todavía existe, pero el Angular CLI ya no lo genera por defecto. Hoy la forma recomendada de obtener el servicio dentro de un it es TestBed.inject(NamesService), que además es type safe:

it('should be created', () => {
  const service = TestBed.inject(NamesService);
  expect(service).toBeTruthy();
});

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

Desventaja: Requiere repetir la misma línea en cada prueba.

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

Podemos valernos de Angular TestBed para evitar repetir esa línea en cada bloque. Algo así:

let service: NamesService;

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

  // Obtiene una instancia de NamesService con TestBed.inject()
  // (reemplaza al antiguo TestBed.get(), que fue eliminado en Angular 14)
  service = TestBed.inject(NamesService);
});

// El servicio ya está disponible en cada prueba
it('should be created', () => {
  expect(service).toBeTruthy();
});

Nota: Si vienes de tutoriales antiguos verás TestBed.get(NamesService). Ese método se marcó como deprecado en Angular 9 y se eliminó por completo en Angular 14, así que en cualquier versión soportada hoy no compila. Usa siempre TestBed.inject(NamesService), que es la API actual y type safe.

Ventaja: Cada bloque it recibe una versión nueva del servicio, evitando arrastrar un servicio contaminado 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 con beforeAll:

let service: NamesService;

// Antes de todas las pruebas, obtiene una sola instancia de NamesService
beforeAll(() => {
  TestBed.configureTestingModule({
    providers: [NamesService],
  });
  service = TestBed.inject(NamesService);
});

// Verifica que el valor de myVar se establece en 13
it('should set myVar to 13', () => {
  service.setMyVar(13);
  expect(service.myVar).toBe(13);
});

// Verifica que myVar sigue en 13 (estado persistido) y luego lo cambia a 12
it('should keep myVar at 13 and then set it to 12', () => {
  expect(service.myVar).toBe(13);
  service.setMyVar(12);
  expect(service.myVar).toBe(12);
});

Fíjate que el segundo test espera que myVar siga valiendo 13 porque la instancia es la misma del primer test. Esto solo funciona porque ambos comparten estado.

Cuidado: Compartir estado con beforeAll crea tests dependientes del orden, lo cual es un anti patrón si se generaliza. Si ejecutas los it en otro orden o de forma aislada, fallarán. Trátalo como una técnica deliberada y acotada (por ejemplo, para seguir la trazabilidad de un flujo CRUD), no como tu opción por defecto. En la mayoría de los casos, una instancia fresca por test (Forma 2) es más segura y fácil de mantener.

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

Desventaja: Puede hacer que los tests sean más difíciles de mantener si no se maneja con cuidado la persistencia del estado, además de acoplarlos al orden de ejecución.

Conclusiones

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

it('should be created', inject([NamesService], (service: NamesService) => { /* ... */ }));
beforeEach(() => service = TestBed.inject(NamesService));
beforeAll(() => service = TestBed.inject(NamesService));

La Forma 1 es el patrón heredado del CLI antiguo; hoy preferimos TestBed.inject() dentro del it. La Forma 2 te da una instancia limpia en cada prueba, y la Forma 3 comparte una sola instancia cuando necesitas persistir estado, asumiendo el costo de tests dependientes del orden.

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 y tests dependientes del orden.

Resumen en 3 puntos

  1. Existen tres formas principales de inyectar servicios en unit tests con Angular, cada una con sus ventajas y desventajas, y todas usan TestBed.inject() en lugar del eliminado TestBed.get().
  2. La elección del enfoque dependerá de si necesitas una instancia nueva del servicio en cada bloque it o si necesitas persistir el estado a lo largo de los tests.
  3. Practicar con diferentes escenarios te 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 compartirlo usando los links a las redes sociales aquí abajo.

Sebastian Gomez

Sebastian Gomez

Creador de contenido principalmente acerca de tecnología.

Leave a Reply

0 Comments

Advertisements

Related Posts

Categorias