Alejandría

Busca en Alejandría

Herramientas

Pact

Pruebas de contrato dirigidas por el consumidor para integrar servicios sin entornos compartidos.

01

En pocas palabras

Herramienta de contract testing en la que el consumidor registra sus expectativas sobre una API en un contrato, y el proveedor lo verifica contra su implementación real.

Qué problema resuelve

Pruebas end-to-end lentas y frágiles en entornos compartidos, o cambios de API que rompen a consumidores sin que nadie lo detecte antes de producción.

Cuándo utilizarlo

Servicios que se comunican por HTTP o mensajes y se despliegan de forma independiente. Ejemplo: la app móvil y la API de solicitudes evolucionan en equipos distintos.

02

De la idea a la práctica

Funcionamiento, ejemplos y decisiones para pasar al siguiente nivel.

Cómo funciona

  1. Consumidor: una prueba unitaria define la interacción esperada (petición y respuesta mínima) contra un mock de Pact. Al pasar, se genera un archivo de contrato (pact).
  2. Broker: el contrato se publica en un Pact Broker (o PactFlow) asociado a la versión y rama del consumidor.
  3. Proveedor: su pipeline descarga los contratos relevantes y los reproduce contra la API real, preparando el estado necesario.
  4. can-i-deploy: antes de desplegar, cualquiera de los dos pregunta al broker si la versión es compatible con lo que hay en el entorno destino.

Ejemplo de prueba del consumidor (JavaScript)

import { PactV4, MatchersV3 } from '@pact-foundation/pact';
const { like, string } = MatchersV3;

const provider = new PactV4({
  consumer: 'app-movil',
  provider: 'api-solicitudes',
});

it('obtiene el estado de una solicitud', () =>
  provider
    .addInteraction()
    .given('existe la solicitud 42 en evaluación')
    .uponReceiving('una consulta de estado')
    .withRequest('GET', '/v1/solicitudes/42')
    .willRespondWith(200, (b) =>
      b.jsonBody({ id: string('42'), estado: like('EN_EVALUACION') }),
    )
    .executeTest(async (mock) => {
      const res = await obtenerEstado(mock.url, '42');
      expect(res.estado).toBe('EN_EVALUACION');
    }));

like verifica el tipo, no el valor exacto, para que el contrato no sea frágil. El texto de given es el provider state que el proveedor debe saber preparar.

Verificación en el proveedor y despliegue

pact-broker can-i-deploy \
  --pacticipant api-solicitudes --version "$GIT_SHA" \
  --to-environment produccion

Tras desplegar, se registra con record-deployment para que el broker conozca qué versiones conviven en cada entorno.

Cuándo no usarlo

  • APIs públicas con consumidores desconocidos: mejor esquemas versionados (OpenAPI) y pruebas de compatibilidad.
  • Equipos que no controlan al proveedor (SaaS externos).
  • Para validar lógica de negocio del proveedor: el contrato prueba la forma de la integración, no las reglas.

Verificación del proveedor con Spring Boot

@Provider("api-solicitudes")
@PactBroker   // URL y credenciales por propiedades o variables de entorno
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ContratosProveedorTest {

    @LocalServerPort int puerto;
    @Autowired SolicitudRepository repo;

    @BeforeEach
    void destino(PactVerificationContext ctx) {
        ctx.setTarget(new HttpTestTarget("localhost", puerto));
    }

    @TestTemplate
    @ExtendWith(PactVerificationInvocationContextProvider.class)
    void verificarInteracciones(PactVerificationContext ctx) {
        ctx.verifyInteraction();
    }

    @State("existe la solicitud 42 en evaluación")
    void solicitud42() {
        repo.save(Solicitud.enEvaluacion("42"));
    }
}

Cada @State corresponde a un given(...) del consumidor. El proveedor prepara sus propios datos; no hace falta un entorno compartido con datos «mágicos».

Contratos de mensajes (Kafka, Pub/Sub)

Pact también verifica eventos asíncronos: el consumidor declara qué forma de mensaje espera y el proveedor demuestra que es capaz de producirlo, sin un broker real en la prueba.

Lado Qué prueba
Consumidor Que su handler procesa correctamente un mensaje con esa forma
Proveedor Que la función que construye el evento genera un mensaje compatible

Complementa (no sustituye) al registro de esquemas: el registro impone compatibilidad técnica del esquema completo; Pact verifica lo que cada consumidor usa realmente.

Flujo con ramas y entornos

PR en consumidor ── publica contrato (versión = SHA, rama) ──► Broker
                                                               │ webhook
Proveedor ◄── ejecuta verificación del contrato nuevo ─────────┘
  │ publica resultado
  ▼
can-i-deploy (consumidor) → ¿compatible con lo que hay en producción? → desplegar
record-deployment → el broker sabe qué versión está en cada entorno

Con pending pacts y WIP pacts, un contrato nuevo de un consumidor no rompe el build del proveedor hasta que se ha verificado una vez, lo que evita bloqueos entre equipos.

Errores frecuentes

  • Contratos que copian respuestas completas: cualquier cambio del proveedor los rompe.
  • Usar Pact para probar reglas de negocio del proveedor.
  • No ejecutar can-i-deploy: los contratos se verifican pero nadie impide desplegar una versión incompatible.

Criterios de implementación

Empieza por la integración que más se rompe. Mantén los contratos mínimos y sin datos específicos. Integra can-i-deploy como paso bloqueante del pipeline y usa ramas y entornos en el broker. Es una buena fitness function de evolución de API y reduce dependencia de pruebas end-to-end.

Continúa en las fuentes

Documentación oficial para verificar detalles y explorar más.