Más allá de los tests unitarios básicos, existen conceptos clave para asegurar la calidad en sistemas complejos y microservicios.
A menudo confundimos todo con "Mocks", pero hay diferencias importantes.
- Objetivo: Verificar comportamiento.
- Qué hacen: Esperan que se les llame de cierta forma. Si no se les llama, el test falla.
- Herramienta: Mockito.
- Ejemplo: "Verificar que al guardar un usuario, se llamó al método
sendEmaildelEmailServiceexactamente 1 vez".
// Mockito
verify(emailService, times(1)).sendEmail(anyString());- Objetivo: Implementación ligera pero funcional.
- Qué hacen: Tienen lógica real simplificada. No usan bases de datos ni redes.
- Uso: Reemplazar una base de datos lenta por un
HashMapen memoria para tests ultra-rápidos. - Ejemplo:
public class InMemoryUserRepository implements UserRepository {
private Map<Long, User> db = new HashMap<>();
public void save(User user) { db.put(user.getId(), user); }
public User findById(Long id) { return db.get(id); }
}- Objetivo: Proveer respuestas predefinidas (hardcoded).
- Qué hacen: "Si te llaman con X, devuelve Y". No verifican nada, solo ayudan a que el test pase.
Los tests que solo prueban el "camino feliz" (todo funciona bien) no sirven de mucho en producción. Lo valioso es testear qué pasa cuando las cosas fallan.
- Timeouts: ¿Qué hace mi servicio si la API externa tarda 10 segundos en responder? ¿Lanza una excepción controlada o se cuelga?
- Errores 500: ¿Si el servicio de Pagos devuelve 500, mi servicio de Pedidos hace rollback de la transacción?
- Datos Corruptos: ¿Qué pasa si recibo un JSON mal formado?
Usando Mockito para forzar excepciones:
// Simular que la DB lanza una excepción de conexión
when(repository.save(any())).thenThrow(new DataAccessException("DB Down"));
// Verificar que mi servicio maneja el error y no explota
assertThrows(ServiceException.class, () -> myService.crearPedido(pedido));En microservicios, el mayor miedo es: "Si cambio la API del Servicio A, ¿romperé al Servicio B?".
Si en los tests del Servicio B "mockeas" al Servicio A, estás asumiendo que A responde de cierta forma. Pero si A cambia su respuesta en la realidad, tus tests seguirán pasando (porque usas un mock), pero en producción todo explotará.
Es una técnica para garantizar que dos servicios se entienden sin necesidad de levantar ambos a la vez (que sería un test E2E lento).
- Consumidor (Consumer): Define un Contrato (Pact). "Espero que si hago GET /users/1, me devuelvas un JSON con
idyname". - Productor (Provider): En su pipeline de CI/CD, se descarga ese contrato y ejecuta tests automáticos para verificar que realmente cumple lo que el consumidor espera.
- Resultado: Si el Productor cambia el nombre del campo
nameafullName, su build fallará avisándole que rompería al Consumidor.
Tip
Contract Testing reemplaza la necesidad de muchos tests E2E (End-to-End) que son lentos, costosos y frágiles.