Pruebas de Componentes con Data Test ID y Selectores CSS

Clase 16 de 23Curso de Pruebas Unitarias en Angular

Resumen

La realización de pruebas en componentes de interfaz gráfica es fundamental para garantizar que nuestras aplicaciones funcionen correctamente. Dominar las técnicas de consulta (query) a elementos renderizados nos permite verificar que la interfaz se presenta tal como esperamos. En este artículo exploraremos diferentes métodos para realizar pruebas efectivas en componentes, enfocándonos en buenas prácticas que te ayudarán a crear tests más robustos y mantenibles.

¿Cómo realizar consultas efectivas a componentes renderizados?

Cuando desarrollamos pruebas para componentes de interfaz, necesitamos verificar que los elementos visuales se muestran correctamente. Existen varias técnicas para consultar estos elementos y comprobar su contenido.

En nuestro ejemplo, queremos verificar que el título de un producto se muestra correctamente en la interfaz. Para ello, primero configuramos un producto de prueba:

// Creamos un producto de prueba constante para usar en todas las pruebas
const mockProduct = /* producto generado */;

// Configuración previa a las pruebas (beforeEach)
// Inyectamos el producto al componente

Consulta por tipo de elemento HTML

La forma más básica de consultar un elemento es por su tipo HTML. Por ejemplo, podemos verificar si existe un elemento h3 con el título del producto:

it('debería mostrar el título del producto', () => {
  // Detectamos cambios para disparar el render
  fixture.detectChanges();
  
  // Buscamos un elemento h3 que contenga el título del producto
  const element = fixture.nativeElement.querySelector('h3');
  expect(element.textContent).toContain(mockProduct.title);
});

Esta prueba pasa correctamente, pero tiene un problema importante: si en el futuro cambiamos el elemento de h3 a h4 u otro tipo, la prueba fallará aunque la funcionalidad siga siendo correcta.

Uso de data-testid para pruebas más robustas

Una mejor práctica es utilizar atributos data-testid para identificar elementos específicamente para pruebas:

<!-- En el componente HTML -->
<h3 data-testid="product-title">{{ product.title }}</h3>

Esto nos permite consultar el elemento independientemente de su tipo HTML:

it('debería mostrar el título del producto', () => {
  fixture.detectChanges();
  
  // Consultamos por el atributo data-testid
  const element = fixture.nativeElement.querySelector('[data-testid="product-title"]');
  expect(element.textContent).toContain(mockProduct.title);
});

Uso de funciones auxiliares para consultas más limpias

Los frameworks de testing suelen proporcionar funciones auxiliares para hacer estas consultas más legibles y menos propensas a errores. Por ejemplo, la función byTestId:

import { byTestId } from '@testing-library/angular';

it('debería mostrar el título del producto', () => {
  fixture.detectChanges();
  
  // Usamos la función auxiliar byTestId
  const element = fixture.nativeElement.querySelector(byTestId('product-title'));
  expect(element.textContent).toContain(mockProduct.title);
});

Esta aproximación tiene varias ventajas:

  • Código más legible y menos propenso a errores de escritura
  • Independencia del marcado HTML subyacente
  • Compatibilidad con otros frameworks de testing

¿Por qué usar data-testid es una buena práctica en testing?

El uso de atributos data-testid se ha convertido en un estándar en el mundo del testing por varias razones:

  1. Desacopla las pruebas de la estructura HTML: Si cambias un h3 por un div, tus pruebas seguirán funcionando.
  2. Proporciona intención clara: Indica explícitamente qué elementos están destinados a ser probados.
  3. Es compatible con múltiples frameworks: Esta técnica funciona con Testing Library, Cypress, Selenium, Playwright y otros.
  4. Facilita las pruebas end-to-end: Proporciona selectores consistentes entre pruebas unitarias y e2e.

Consideraciones importantes al usar data-testid

Al implementar esta estrategia, debes tener en cuenta algunas consideraciones:

  • Evita duplicados: Los data-testid deben ser únicos dentro del contexto de la prueba.
  • Cuidado con elementos repetidos: No uses el mismo data-testid en elementos generados dinámicamente (como en bucles *ngFor).
  • Selectores específicos: Si necesitas probar elementos repetidos, combina data-testid con otros selectores para mayor especificidad.
// Para elementos repetidos, puedes usar selectores más específicos
const specificElement = fixture.nativeElement.querySelector('.product-jacket [data-testid="product-title"]');

¿Qué otras técnicas de testing son útiles para componentes?

Además de las consultas por data-testid, existen otras técnicas complementarias:

  • Testing de eventos: Verificar que los eventos se disparan correctamente.
  • Testing de estados: Comprobar que el componente cambia de estado adecuadamente.
  • Mocking de servicios: Simular respuestas de servicios para probar diferentes escenarios.
it('debería actualizar el precio cuando cambia la cantidad', () => {
  // Configuración inicial
  component.quantity = 1;
  fixture.detectChanges();
  
  // Acción
  component.quantity = 2;
  fixture.detectChanges();
  
  // Verificación
  const priceElement = fixture.nativeElement.querySelector(byTestId('product-price'));
  expect(priceElement.textContent).toContain(mockProduct.price * 2);
});

Las pruebas de componentes son esenciales para garantizar la calidad de nuestras aplicaciones. Utilizando técnicas como los atributos data-testid, podemos crear pruebas más robustas y mantenibles que resistan cambios en la estructura HTML. Estas prácticas no solo son útiles en pruebas unitarias, sino que también se aplican en pruebas end-to-end con herramientas como Cypress o Playwright. ¿Has implementado estas técnicas en tus proyectos? Comparte tu experiencia en los comentarios.