Testing de Servicios en Angular con Jest y Spectator
Clase 8 de 23 • Curso de Pruebas Unitarias en Angular
Resumen
La prueba de servicios en Angular es un componente esencial para garantizar la calidad y robustez de nuestras aplicaciones. Al testear estos artefactos que no interactúan directamente con la UI, podemos detectar errores tempranamente y asegurar que nuestro código funcione como esperamos. En este artículo, exploraremos cómo implementar pruebas unitarias para servicios en Angular utilizando Spectator y Jest, con un enfoque práctico y ejemplos concretos.
¿Cómo configurar el entorno para testear servicios en Angular?
Antes de sumergirnos en la implementación de pruebas para servicios, es importante entender la configuración básica necesaria. Spectator proporciona herramientas útiles para simplificar este proceso, especialmente con su función createServiceFactory
.
Configuración inicial con Spectator
Spectator ofrece una forma elegante de crear instancias de servicios para pruebas:
const createService = createServiceFactory({
service: CartService,
providers: []
});
describe('CartService', () => {
let spectator: SpectatorService<CartService>;
beforeEach(() => {
spectator = createService();
});
it('should be created', () => {
expect(spectator.service).toBeTruthy();
});
});
Este patrón es particularmente útil porque:
- Crea una nueva instancia del servicio para cada prueba
- Aísla cada caso de prueba, evitando interferencias entre ellos
- Proporciona un entorno limpio para cada escenario
El método beforeEach
es crucial ya que garantiza que cada prueba tenga su propia instancia del servicio, permitiendo modificaciones sin afectar otras pruebas.
Creando nuestro primer archivo de prueba
Para comenzar a testear un servicio, debemos crear un archivo con la nomenclatura adecuada. Por ejemplo, para probar el servicio del carrito:
// cart.service.spec.ts
import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest';
import { CartService } from './cart.service';
describe('CartService', () => {
let spectator: SpectatorService<CartService>;
const createService = createServiceFactory(CartService);
beforeEach(() => {
spectator = createService();
});
it('should be created', () => {
expect(spectator.service).toBeTruthy();
});
});
Para ejecutar específicamente esta prueba, podemos usar el comando:
ng test --test-name=CartService
¿Cómo implementar pruebas efectivas para un servicio de carrito?
Una vez configurado el entorno básico, podemos comenzar a escribir pruebas más específicas para nuestro servicio de carrito.
Creando mocks de productos
Para probar la funcionalidad de agregar productos al carrito, necesitamos crear mocks que simulen productos reales:
it('should add a product to the cart', () => {
const mockProduct = {
id: 1,
title: 'Test Product',
price: 10,
description: 'Test description',
category: {
id: 1,
name: 'Test Category',
image: ''
},
images: [],
slug: 'test-product',
creationAt: '2023-01-01'
};
spectator.service.addToCart(mockProduct);
expect(spectator.service.cart()).toHaveLength(1);
expect(spectator.service.total()).toBe(10);
});
Este test verifica dos comportamientos clave:
- Que el producto se agregue correctamente al carrito
- Que el total se calcule adecuadamente basado en el precio del producto
Ampliando la cobertura con casos límite
Para asegurar la robustez de nuestro servicio, es importante probar casos límite o edge cases:
describe('Edge cases', () => {
const mockProduct1 = {
id: 1,
title: 'Product 1',
price: 10,
description: 'Description 1',
category: { id: 1, name: 'Category 1', image: '' },
images: [],
slug: 'product-1',
creationAt: '2023-01-01'
};
const mockProduct2 = {
id: 2,
title: 'Product 2',
price: 20,
description: 'Description 2',
category: { id: 1, name: 'Category 1', image: '' },
images: [],
slug: 'product-2',
creationAt: '2023-01-01'
};
it('should initialize with empty cart and zero total', () => {
expect(spectator.service.cart()).toEqual([]);
expect(spectator.service.total()).toBe(0);
});
it('should correctly calculate total with multiple products', () => {
spectator.service.addToCart(mockProduct1);
spectator.service.addToCart(mockProduct2);
expect(spectator.service.cart()).toHaveLength(2);
expect(spectator.service.total()).toBe(30);
});
it('should handle products with zero price', () => {
const zeroProduct = {...mockProduct1, price: 0};
spectator.service.addToCart(zeroProduct);
expect(spectator.service.total()).toBe(0);
});
it('should handle products with negative price', () => {
const negativeProduct = {...mockProduct1, price: -10};
spectator.service.addToCart(negativeProduct);
expect(spectator.service.total()).toBe(-10);
});
});
Estos casos de prueba adicionales nos ayudan a:
- Verificar el estado inicial del carrito
- Comprobar cálculos con múltiples productos
- Manejar casos especiales como precios cero o negativos
Es importante notar que el caso de precios negativos podría revelar una vulnerabilidad en nuestro servicio. Quizás deberíamos implementar una validación para rechazar productos con precios negativos.
¿Cómo aprovechar la IA para mejorar nuestras pruebas?
La inteligencia artificial puede ser una herramienta poderosa para generar casos de prueba adicionales que quizás no hayamos considerado.
Generando casos de prueba con IA
Utilizando modelos como Claude o GPT, podemos solicitar la generación de pruebas adicionales proporcionando el contexto del servicio:
Generar tests para CartService y edge cases
La IA puede sugerir casos como:
- Pruebas con valores decimales
- Manejo de límites numéricos
- Escenarios de concurrencia
- Casos de uso inusuales pero posibles
Los modelos de IA especializados en código como Claude o GitHub Copilot son particularmente efectivos para esta tarea, ya que comprenden mejor la estructura y patrones de pruebas.
Refinando el código basado en pruebas generadas por IA
Las pruebas generadas por IA pueden revelar posibles mejoras en nuestro servicio:
// Posible mejora en el servicio basada en pruebas de IA
addToCart(product: Product) {
if (product.price < 0) {
console.warn('Attempted to add product with negative price');
return; // Prevent adding products with negative prices
}
this.cart.update(state => [...state, product]);
}
Este enfoque nos permite:
- Descubrir casos límite no considerados inicialmente
- Mejorar la robustez de nuestro código
- Aumentar la cobertura de pruebas de manera eficiente
La combinación de pruebas manuales bien pensadas con sugerencias de IA puede resultar en una suite de pruebas más completa y efectiva.
El testing de servicios en Angular es fundamental para garantizar aplicaciones robustas y libres de errores. Mediante el uso de herramientas como Spectator y Jest, junto con técnicas de mocking adecuadas, podemos crear pruebas efectivas que verifiquen el comportamiento esperado de nuestros servicios. Además, la incorporación de IA en nuestro proceso de testing puede ayudarnos a descubrir casos límite y mejorar la calidad general de nuestro código. ¿Has utilizado alguna de estas técnicas en tus proyectos? ¡Comparte tu experiencia en los comentarios!