Contenido del curso

Pruebas de Servicios y Dependencias

Pruebas de Mocking y Deferred Components en Angular

Resumen

Probar componentes con carga diferida en Angular tiene un truco escondido: el IntersectionObserver API no existe en el entorno de pruebas y rompe la consola aunque los tests pasen. Aquí aprendes a mockear esa API, controlar manualmente el bloque defer y verificar que tu componente diferido se renderice como esperas.

Por qué falla IntersectionObserver en los tests de Angular

Cuando corres pruebas unitarias sobre un componente que usa @defer con la condición on viewport, la consola te lanza un error: IntersectionObserver is not defined. Los tests pasan, pero ese mensaje incomoda y revela algo importante sobre cómo Angular carga estos bloques.

Qué hace Angular por detrás cuando usas defer on viewport

Angular utiliza la IntersectionObserver API, una API nativa del navegador que detecta cuándo un elemento entra al viewport del usuario. Es la misma lógica que aplica para APIs como navigator.geolocation o fetch: existen del lado del browser, no del lado del runtime de pruebas [03:15].

El entorno de testing no es un navegador real, es un entorno controlado que monta componentes pero no expone todas las APIs nativas. Por eso, cuando el componente diferido intenta inicializarse, no encuentra IntersectionObserver y revienta.

¿Qué es IntersectionObserver? Es una API del navegador que avisa cuándo un elemento entra o sale del viewport. Angular la usa internamente para decidir cuándo cargar bloques defer on viewport.

Cómo crear un mock de IntersectionObserver

La solución es suplantar la API directamente sobre el objeto window, igual que harías con fetch. El mock reemplaza la API nativa con funciones vacías que simulan los métodos esperados: observe, unobserve y disconnect.

typescript (window as any).IntersectionObserver = jest.fn().mockImplementation(() => ({ observe: jest.fn(), unobserve: jest.fn(), disconnect: jest.fn(), }));

Con ese mocking, el error desaparece de la consola y las pruebas siguen pasando limpias. Como las funciones están envueltas en jest.fn(), también podrías espiarlas más adelante si necesitas verificar que algo las llamó.

Cómo probar manualmente un bloque defer en pruebas unitarias

Una vez resuelto el error, viene la pregunta de fondo: cómo verificar que el componente dentro del @defer realmente se renderiza. En unit tests no puedes mover el scroll, así que necesitas tomar control manual del bloque diferido.

Configurar deferBlockBehavior en modo manual

En la creación del componente con Spectator (o TestBed), agregas el parámetro deferBlockBehavior y lo defines como Manual. Este enum viene de @angular/core/testing [07:30].

typescript import { DeferBlockBehavior } from '@angular/core/testing';

const createComponent = createComponentFactory({ component: ProductDetailComponent, deferBlockBehavior: DeferBlockBehavior.Manual, });

Con esto le dices al runtime: no intentes detectar viewport, yo te aviso cuándo cargar. Eso te da control total para probar cada estado del bloque.

Renderizar el bloque defer con renderComplete

Dentro del it, llamas a spectator.deferBlock() y le pasas el método que quieres simular. Las opciones cubren todos los estados que puede tener un @defer:

  • renderComplete: simula que el contenido principal terminó de cargar.
  • renderLoading: simula el estado de carga.
  • renderError: simula un error en la carga.
  • renderPlaceholder: simula el placeholder antes de cargar.

Como renderComplete es asíncrono, el test se vuelve async y le agregas await:

typescript it('should load related products', async () => { spectator.detectChanges(); await spectator.deferBlock().renderComplete(); expect(spectator.query(RelatedComponent)).toBeTruthy(); });

¿Cómo verifico que un componente Angular se renderizó dentro de un defer? Pasa la clase del componente directamente a spectator.query(), sin necesidad de un data-testid. Funciona cuando es un componente Angular, no un tag HTML genérico.

Por qué consultar por clase del componente y no por testid

Cuando el elemento renderizado es un componente Angular como RelatedComponent, puedes pasarle la clase directamente a query. Esto es más limpio que añadir un data-testid y te ahorra acoplar el test al markup interno del hijo.

Qué probar más allá del renderComplete

El modo manual te abre la puerta a escenarios que en producción son difíciles de reproducir.

  • Verifica el estado de loading mientras el bloque está cargando.
  • Valida el mensaje o componente de error cuando algo falla.
  • Confirma que el placeholder se muestra antes de iniciar la carga.
  • Comprueba que servicios como getOneBySlug fueron llamados con los argumentos correctos.

Cada uno de esos casos se prueba reemplazando renderComplete() por el método correspondiente y consultando el DOM resultante.

En la siguiente clase viene un reto distinto: probar la galería de imágenes, donde los data-testid se duplican y toca buscar otra estrategia para identificar elementos. ¿Has tenido que lidiar con IntersectionObserver en tus tests? Cuéntame cómo lo resolviste.