TL;DR
- Los contratos dirigidos por el consumidor permiten a equipos móviles definir expectativas de API sin esperar al backend
- Los tests Pact se ejecutan en milisegundos vs segundos en tests de integración, detectando cambios disruptivos antes del despliegue
- La verificación can-i-deploy es tu red de seguridad—nunca despliegues sin ella
Ideal para: Equipos móviles consumiendo APIs de microservicios, equipos con cambios frecuentes de API
Omitir si: Backend monolítico único, APIs estables con cambios raros
Tiempo de lectura: 15 minutos
El contract testing de API asegura que las aplicaciones móviles y servicios backend se comuniquen correctamente sin requerir tests de integración completos. Este enfoque detecta cambios disruptivos temprano, permite despliegue independiente y mantiene compatibilidad retroactiva entre versiones de API. Mientras que los fundamentos de testing de API se centran en validar endpoints individuales, el contract testing adopta un enfoque dirigido por el consumidor para garantizar una integración fluida.
Para maximizar la efectividad del contract testing, es esencial comprender cómo se integra con otras prácticas de testing. Los mock servers en desarrollo móvil proporcionan la base para simular providers durante el desarrollo, mientras que las estrategias de versionado de API definen cómo evolucionar contratos sin romper clientes existentes.
¿Qué es Contract Testing?
El contract testing verifica que dos sistemas separados (consumidor y proveedor) estén de acuerdo en el formato de los mensajes que intercambian. A diferencia de los tests end-to-end, los contract tests se ejecutan independientemente para cada servicio.
Testing de Integración Tradicional vs Contract Testing
Testing de Integración Tradicional:
App Móvil → Stack Backend Completo → Base de Datos
- Lento (segundos a minutos)
- Inestable (red, problemas de entorno)
- Requiere infraestructura completa
- Difícil reproducir casos extremos
Contract Testing:
App Móvil → Contract Stub (Pact Mock)
Backend API → Contract Verification (Pact Provider)
- Rápido (milisegundos)
- Confiable (sin dependencias de red)
- Se ejecuta en pipelines CI/CD
- Simulación fácil de casos extremos
Conceptos Básicos
Contratos Dirigidos por el Consumidor
El consumidor (app móvil) define expectativas para el comportamiento del proveedor (backend API). El proveedor debe honrar estos contratos. Esto es particularmente crucial en estrategias modernas de testing móvil donde las apps deben adaptarse a servicios backend en evolución.
Beneficios:
- Equipos móviles no bloqueados por retrasos del backend
- Cambios de API validados antes del despliegue
- Comunicación clara entre equipos
- Estrategias de versionado y deprecación
Flujo de Trabajo Pact
1. Consumidor (Móvil) escribe tests Pact
↓
2. Genera archivo de contrato Pact (JSON)
↓
3. Contrato publicado en Pact Broker
↓
4. Proveedor (Backend) verifica contrato
↓
5. Resultados publicados en Pact Broker
↓
6. Verificación Can-I-Deploy antes del release
Pact para Aplicaciones Móviles
Implementación Android
Setup (build.gradle.kts):
dependencies {
testImplementation("au.com.dius.pact.consumer:junit5:4.6.1")
testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
}
Test Pact del Consumidor (UserApiPactTest.kt):
import au.com.dius.pact.consumer.dsl.PactBuilder
import au.com.dius.pact.consumer.junit5.PactConsumerTestExt
import au.com.dius.pact.consumer.junit5.PactTestFor
import au.com.dius.pact.core.model.V4Pact
import au.com.dius.pact.core.model.annotations.Pact
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
@ExtendWith(PactConsumerTestExt::class)
@PactTestFor(providerName = "UserService")
class UserApiPactTest {
@Pact(consumer = "MobileApp")
fun getUserByIdPact(builder: PactBuilder): V4Pact {
return builder
.usingLegacyDsl()
.given("usuario 123 existe")
.uponReceiving("una petición para usuario 123")
.path("/api/users/123")
.method("GET")
.headers(mapOf("Accept" to "application/json"))
.willRespondWith()
.status(200)
.headers(mapOf("Content-Type" to "application/json"))
.body("""
{
"id": 123,
"username": "juan_perez",
"email": "juan@ejemplo.com",
"createdAt": "2024-01-15T10:30:00Z"
}
""".trimIndent())
.toPact()
.asV4Pact().get()
}
@Test
@PactTestFor(pactMethod = "getUserByIdPact", port = "8080")
fun testGetUserById() {
val apiClient = ApiClient("http://localhost:8080")
runBlocking {
val user = apiClient.getUser(123)
assertEquals(123, user.id)
assertEquals("juan_perez", user.username)
}
}
}
Verificación del Proveedor (Backend)
Verificación Spring Boot Provider
La verificación del proveedor es esencial en arquitectura de API de microservicios, donde múltiples servicios deben mantener compatibilidad de contratos.
Setup (build.gradle.kts):
dependencies {
testImplementation("au.com.dius.pact.provider:junit5spring:4.6.1")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
Test del Proveedor (UserServiceProviderTest.kt):
@SpringBootTest
@AutoConfigureMockMvc
@Provider("UserService")
@PactBroker(host = "pact-broker.ejemplo.com")
class UserServiceProviderTest {
@Autowired
private lateinit var mockMvc: MockMvc
@Autowired
private lateinit var userRepository: UserRepository
@BeforeEach
fun setUp(context: PactVerificationContext) {
context.target = MockMvcTestTarget(mockMvc)
}
@TestTemplate
@ExtendWith(PactVerificationInvocationContextProvider::class)
fun pactVerificationTestTemplate(context: PactVerificationContext) {
context.verifyInteraction()
}
@State("usuario 123 existe")
fun userExistsState() {
val user = User(
id = 123,
username = "juan_perez",
email = "juan@ejemplo.com"
)
userRepository.save(user)
}
}
Pact Broker Setup
Configuración Docker Compose
version: '3'
services:
postgres:
image: postgres:15
environment:
POSTGRES_USER: pact
POSTGRES_PASSWORD: pact
POSTGRES_DB: pact_broker
pact-broker:
image: pactfoundation/pact-broker:latest
ports:
- "9292:9292"
depends_on:
- postgres
environment:
PACT_BROKER_DATABASE_USERNAME: pact
PACT_BROKER_DATABASE_PASSWORD: pact
PACT_BROKER_DATABASE_HOST: postgres
Versionado y Compatibilidad Retroactiva
Manejo de Cambios de API
Cambio No Disruptivo (Añadir Campo Opcional):
// Contrato antiguo
{
"id": 123,
"username": "juan_perez",
"email": "juan@ejemplo.com"
}
// Nuevo contrato (compatible retroactivamente)
{
"id": 123,
"username": "juan_perez",
"email": "juan@ejemplo.com",
"avatar": "https://cdn.ejemplo.com/avatar.jpg" // Opcional, nuevo campo
}
Cambio Disruptivo (Eliminar Campo):
// El proveedor debe soportar contrato antiguo durante período de transición
@GetMapping("/api/users/{id}")
fun getUser(@PathVariable id: Long, @RequestHeader("API-Version") version: String?): UserResponse {
val user = userRepository.findById(id)
return when (version) {
"v1" -> UserResponseV1(user) // Incluye campos deprecados
"v2", null -> UserResponseV2(user) // Nuevo contrato
else -> throw UnsupportedVersionException()
}
}
Mejores Prácticas
1. Probar Escenarios Reales
@Pact(consumer = "MobileApp")
fun rateLimitedRequestPact(builder: PactBuilder): V4Pact {
return builder
.usingLegacyDsl()
.given("límite de tasa excedido para usuario 123")
.uponReceiving("petición que activa límite de tasa")
.path("/api/products")
.method("GET")
.willRespondWith()
.status(429)
.body("""
{
"error": "rate_limit_exceeded",
"message": "Demasiadas peticiones",
"retryAfter": 60
}
""".trimIndent())
.toPact()
.asV4Pact().get()
}
2. Usar Matchers para Contratos Flexibles
Los matchers permiten validación flexible de contratos, similar a cómo REST Assured maneja aserciones de API, pero a nivel de contrato en lugar de runtime.
import au.com.dius.pact.consumer.dsl.LambdaDsl.*
.body(
newJsonBody { obj ->
obj.numberType("id", 123)
obj.stringType("username", "juan_perez")
obj.stringMatcher("email", ".*@ejemplo\\.com", "juan@ejemplo.com")
obj.datetime("createdAt", "yyyy-MM-dd'T'HH:mm:ss'Z'")
}.build()
)
Enfoques Asistidos por IA
El contract testing en 2026 se beneficia de herramientas asistidas por IA para generación y análisis de contratos.
Lo que la IA hace bien:
- Generar tests Pact iniciales desde especificaciones OpenAPI/Swagger
- Identificar casos extremos faltantes en contratos existentes (404s, rate limits, errores de validación)
- Sugerir matchers para validación flexible de contratos
- Detectar potenciales cambios disruptivos analizando diferencias de contratos
Lo que aún necesita humanos:
- Decidir qué escenarios del consumidor son críticos para producción
- Diseñar setup de provider states para datos de prueba realistas
- Balancear rigidez vs flexibilidad de contratos
- Gestionar evolución de contratos entre versiones de API
Prompts útiles:
Analiza esta especificación OpenAPI y genera tests Pact de consumidor para una
app Android cubriendo: respuestas exitosas, 404 not found, 401 unauthorized,
422 errores de validación, y 429 rate limiting.
Revisa mis contratos Pact existentes e identifica brechas en cobertura de
manejo de errores. Sugiere provider states adicionales para testing realista.
Cuándo Usar Contract Testing
Contract testing funciona mejor cuando:
- Múltiples clientes móviles (iOS, Android, Web) consumen la misma API
- Los equipos de backend y móvil despliegan independientemente
- La API cambia frecuentemente durante desarrollo activo
- Los tests de integración son lentos o inestables
- Necesitas confianza antes de desplegar consumidor o proveedor
Considera alternativas cuando:
- Consumidor único con API estable (tests de integración pueden bastar)
- Arquitectura monolítica con frontend acoplado
- Fase de prototipado donde contratos cambian diariamente
- APIs externas que no controlas (usa servidores stub en su lugar)
| Escenario | Recomendación |
|---|---|
| 3+ consumidores, releases frecuentes | Contract testing esencial |
| Consumidor único, API estable | Tests de integración pueden ser suficientes |
| Greenfield con APIs evolucionando | Comienza contratos temprano, itera |
| Monolito legacy | Añade contratos al modularizar |
Midiendo el Éxito
| Métrica | Antes de Contract Testing | Objetivo | Cómo Rastrear |
|---|---|---|---|
| Fallos de integración en prod | Variable | 0 | Monitoreo de errores en producción |
| Tiempo para detectar cambios disruptivos | Días (en staging) | Minutos (en CI) | Duración del pipeline CI |
| Confianza en release móvil | Baja (testing manual) | Alta (automatizado) | Encuesta de equipo |
| Frecuencia de despliegue backend | Semanal | Diario | Logs de despliegue |
Señales de advertencia de que no funciona:
- Contratos pasan pero integración aún falla (contratos muy laxos)
- Cada cambio de backend rompe contratos (contratos muy estrictos)
- Equipos ignorando resultados de can-i-deploy
- Provider states no coinciden con patrones de datos de producción
Conclusión
El contract testing con Pact proporciona:
- Retroalimentación Rápida: Detecta cambios disruptivos en segundos
- Despliegue Independiente: Equipos móviles y backend trabajan en paralelo
- Confianza: Despliega sin miedo a fallos de integración
- Documentación: Contratos API vivos
Hoja de Ruta de Implementación:
- Comienza con flujos críticos de usuario (login, obtención de datos)
- Configura Pact Broker para gestión de contratos
- Integra en pipeline CI/CD
- Añade verificaciones can-i-deploy antes de releases
- Expande cobertura a todas las interacciones de API
El contract testing es esencial para apps móviles que consumen microservicios, permitiendo iteración rápida mientras mantiene estabilidad.
Ver También
- Dominio del Testing de API - Fundamentos completos de testing de API
- Arquitectura de API para Microservicios - Patrones de testing en entornos distribuidos
- Testing Móvil 2025: iOS, Android y Más Allá - Estrategias modernas de testing móvil
- Versionado de API para Móviles - Gestión de versiones de API en aplicaciones móviles
- Mock Servers en Desarrollo Móvil - Simulación de backends para desarrollo ágil
