Mocking y pruebas unitarias en Angular: Inyección de dependencias

Clase 19 de 23Curso de Pruebas Unitarias en Angular

Resumen

La inyección de dependencias y el mocking son técnicas fundamentales en el testing de aplicaciones Angular. Estas prácticas permiten crear pruebas unitarias aisladas y eficientes, evitando dependencias innecesarias que podrían complicar nuestros tests. Dominar estas técnicas es esencial para cualquier desarrollador que busque crear aplicaciones robustas y mantenibles.

¿Cómo realizar pruebas unitarias aisladas en Angular?

Cuando desarrollamos aplicaciones en Angular, es común que nuestros componentes dependan de servicios externos. En pruebas de integración, utilizamos los servicios reales, pero esto puede complicarse cuando estos servicios tienen sus propias dependencias o lógica compleja.

Por ejemplo, en una prueba anterior, se utilizó el servicio real CartService porque tenía una lógica simple. Sin embargo, para componentes más complejos como ProductDetail, que depende de ProductService (que a su vez depende de HttpClient), es preferible utilizar técnicas de mocking.

Las pruebas unitarias deben enfocarse únicamente en la responsabilidad del componente que estamos probando. Si ya hemos verificado el funcionamiento de ProductService en sus propias pruebas, no necesitamos volver a probarlo en las pruebas de ProductDetail.

¿Qué es el mocking y cómo implementarlo en Angular?

El mocking es una técnica que consiste en reemplazar dependencias reales por versiones simuladas que podemos controlar. Esto nos permite:

  1. Aislar el código que estamos probando
  2. Evitar efectos secundarios no deseados
  3. Simular diferentes escenarios fácilmente

Para implementar el mocking en Angular, podemos usar la función MockProvider de @angular/core/testing:

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MockProvider } from '@angular/core/testing';
import { ProductDetailComponent } from './product-detail.component';
import { ProductService } from '../../services/product.service';

describe('ProductDetailComponent', () => {
  let component: ProductDetailComponent;
  let fixture: ComponentFixture<ProductDetailComponent>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [ProductDetailComponent],
      providers: [
        MockProvider(ProductService)
      ]
    }).compileComponents();

    fixture = TestBed.createComponent(ProductDetailComponent);
    component = fixture.componentInstance;
  });

  it('should create', () => {
    // Configuración adicional necesaria
    expect(component).toBeTruthy();
  });
});

MockProvider automáticamente crea un mock del servicio, analizando sus métodos y reemplazándolos con funciones espía (jest.fn()). Esto elimina la necesidad de crear manualmente cada función mock y evita la cadena de dependencias.

¿Cómo manejar inputs requeridos en las pruebas de componentes?

Otro desafío común en las pruebas de componentes es manejar los inputs requeridos. Si un componente necesita ciertos inputs para funcionar correctamente, debemos proporcionarlos en nuestras pruebas:

// Importamos una función para generar productos de prueba
import { generateFakeProduct } from '../../helpers/fake-data';

// En el beforeEach o en el test específico
const fakeProduct = generateFakeProduct();
component.slug = fakeProduct.slug;
fixture.detectChanges();

Para componentes que requieren inputs, debemos:

  1. Proporcionar valores de prueba realistas
  2. Asignarlos antes de detectar cambios
  3. Considerar diferentes escenarios de inputs

En el caso de ProductDetailComponent, necesitamos proporcionar un slug para que el componente pueda funcionar correctamente. Podemos usar una función auxiliar para generar datos de prueba realistas.

¿Cuáles son las ventajas de usar MockProvider en lugar de mocks manuales?

Existen diferentes enfoques para crear mocks en Angular:

  1. Mocks manuales: Crear objetos que imitan la interfaz del servicio
  2. useValue con objetos parciales: Proporcionar implementaciones específicas
  3. MockProvider: Automatizar la creación de mocks

MockProvider ofrece varias ventajas:

  • Automatización: No es necesario definir manualmente cada método
  • Mantenibilidad: Si el servicio cambia, el mock se adapta automáticamente
  • Funciones espía integradas: Facilita verificar si los métodos fueron llamados
// Enfoque manual (más verboso)
{
  provide: ProductService,
  useValue: {
    getProduct: jest.fn(),
    // Otros métodos que necesitamos mockear
  }
}

// Con MockProvider (más conciso)
MockProvider(ProductService)

El enfoque con MockProvider es más limpio y requiere menos código, lo que facilita el mantenimiento de las pruebas a largo plazo.

La elección entre pruebas de integración y pruebas unitarias con mocks depende del contexto. Para componentes simples con pocas dependencias, las pruebas de integración pueden ser apropiadas. Para componentes más complejos, el mocking nos permite crear pruebas más enfocadas y eficientes.

El testing en Angular es un equilibrio entre verificar que los componentes funcionan correctamente de forma aislada y asegurar que interactúan adecuadamente con sus dependencias. Dominar técnicas como el mocking nos permite crear suites de pruebas robustas que nos dan confianza en nuestro código.

¿Has implementado técnicas de mocking en tus proyectos Angular? Comparte tus experiencias y desafíos en los comentarios.