Las pruebas en arquitecturas de microservicios deben abarcar la funcionalidad individual y la interacción entre componentes. Las dependencias remotas, la consistencia eventual y la resiliencia ante fallas obligan a combinar pruebas unitarias, integración, contratos y ejercicios de caos que verifiquen el comportamiento ante situaciones extremas. Este capítulo ofrece estrategias y ejemplos para construir una matriz de pruebas completa.
Las pruebas unitarias validan reglas de negocio aisladas utilizando dobles de prueba para dependencias externas. Aseguran que el servicio pueda evolucionar sin romper comportamientos internos. Las pruebas de integración, por su parte, ejecutan módulos colaborativos (API, repositorios, mensajería) y verifican contratos con recursos reales o simulados.
En microservicios es recomendable ejecutar pruebas unitarias en cada servicio como requisito previo al pipeline de integración. Las pruebas de integración se complementan con despliegues efímeros que levantan el servicio completo, junto con sus dependencias, en contenedores gestionados por Docker Compose o Testcontainers.
El siguiente test verifica la lógica de un agregado de pedidos sin depender de infraestructura.
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock
private PaymentGateway paymentGateway;
@InjectMocks
private OrderService orderService;
@Test
void shouldConfirmOrderWhenPaymentSucceeds() {
CreateOrderCommand command = new CreateOrderCommand("user-1", List.of("SKU-1"));
when(paymentGateway.charge(any())).thenReturn(PaymentResult.approved("PAY-1"));
Order order = orderService.createOrder(command);
assertEquals(OrderStatus.CONFIRMED, order.status());
verify(paymentGateway).charge(any());
}
}
Las dependencias se definen como mocks y se verifican las interacciones. Así se garantiza que la lógica interna responde a las condiciones esperadas.
Para probar la interacción con bases de datos o colas se puede utilizar Testcontainers, que arranca contenedores reales durante la prueba.
@Testcontainers
@SpringBootTest
class InventoryIntegrationTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15.4");
@Autowired
private InventoryRepository inventoryRepository;
@Test
void shouldPersistAndLoadProduct() {
Product product = new Product("SKU-1", 25);
inventoryRepository.save(product);
Optional<Product> found = inventoryRepository.findById("SKU-1");
assertTrue(found.isPresent());
assertEquals(25, found.get().quantity());
}
}
Los contenedores efímeros garantizan que la prueba se ejecute en un entorno similar a producción sin requerir infraestructura compartida.
Los contratos definen cómo se comunican los microservicios. Las pruebas de contrato impulsadas por el consumidor (Consumer-Driven Contracts) permiten que los clientes definan expectativas y los proveedores las verifiquen antes de liberar nuevas versiones. Este enfoque reduce regresiones y evita dependencias implícitas.
Herramientas como Pact, Spring Cloud Contract o Hoverfly automatizan la generación y validación de contratos. Un contrato describe la solicitud y respuesta esperadas, agregando condiciones sobre cabeceras o esquemas JSON.
El siguiente contrato describe la respuesta del servicio de pedidos cuando se consulta un recurso existente.
Contract.make {
description "Devolver órdenes existentes"
request {
method 'GET'
url '/api/orders/123'
headers {
header 'Accept', 'application/json'
}
}
response {
status 200
headers {
header 'Content-Type', 'application/json'
}
body(
id: "123",
status: "CONFIRMED",
total: $(consumer(regex("[0-9]+\\.[0-9]{2}")), producer("120.00"))
)
}
}
Cuando el proveedor genera su build, el plugin de Spring Cloud Contract ejecuta pruebas automáticas verificando que el servicio cumpla el contrato. Los consumidores pueden descargar stubs para simular al proveedor en sus propias pruebas.
Las dependencias de otros servicios, colas o sistemas legados pueden no estar disponibles durante las pruebas. El mocking recrea comportamientos predecibles y acelera el feedback. La simulación es clave para escenarios donde las respuestas dependen de estados complejos o fallos controlados.
Para HTTP es común emplear WireMock, que permite crear endpoints simulados. Para mensajería, librerías como Embedded Kafka o RabbitMQ Test aportan brokers embebidos.
El código siguiente inicia un servidor WireMock para emular respuestas del servicio de envío.
@BeforeAll
static void setupWireMock() {
WireMockServer server = new WireMockServer(options().dynamicPort());
server.start();
configureFor(server.port());
stubFor(get(urlEqualTo("/api/shipping/quote/SKU-1"))
.willReturn(aResponse()
.withHeader("Content-Type", "application/json")
.withBody("{\"sku\":\"SKU-1\",\"price\":12.50}")
.withStatus(200)));
System.setProperty("shipping.base-url", "http://localhost:" + server.port());
}
La simulación permite ejecutar pruebas sin depender de entornos externos, controlar escenarios de error y generar datos deterministas.
La resiliencia requiere validar cómo responde el sistema ante fallas reales: latencia elevada, indisponibilidad, pérdida de nodos o degradación de recursos. El Chaos Engineering aplica experimentos en ambientes controlados para descubrir debilidades antes que lo haga el usuario.
Herramientas como Chaos Monkey, Gremlin o las chaos experiments de Kubernetes permiten inyectar fallas. Es vital definir una hipótesis, monitorear los indicadores clave y recuperar el entorno una vez concluido el experimento.
El siguiente fragmento ejecuta un experimento sencillo utilizando un script que introduce latencia en la capa de red para evaluar la respuesta del sistema.
public class LatencyExperiment {
public static void main(String[] args) throws Exception {
Process tc = new ProcessBuilder("tc", "qdisc", "add", "dev", "eth0",
"root", "netem", "delay", "300ms", "20ms", "distribution", "normal")
.inheritIO()
.start();
tc.waitFor();
// Ejecutar escenarios de prueba mientras la latencia está activa
runCriticalUserJourney();
new ProcessBuilder("tc", "qdisc", "del", "dev", "eth0", "root")
.inheritIO()
.start()
.waitFor();
}
private static void runCriticalUserJourney() {
// Simular transacciones clave y medir impacto en SLOs
}
}
En ambientes productivos se planifican ventanas controladas, se notifica a los equipos implicados y se monitorean SLOs para cortar el experimento si se superan los límites aceptables. Las lecciones aprendidas derivan en mejoras de arquitectura y procedimientos de respuesta.