CodeGym /Cursos /Módulo 5. Spring /Lección 267: Práctica: escribir pruebas de contrato para ...

Lección 267: Práctica: escribir pruebas de contrato para la interacción entre microservicios

Módulo 5. Spring
Nivel 21 , Lección 6
Disponible

Imagina que tienes dos microservicios: el servicio "Consumidor" y el servicio "Producto". El primero quiere obtener información sobre productos del segundo. Parece sencillo, ¿pero qué pasa si el equipo que mantiene el servicio "Producto" decide cambiar la estructura de los objetos JSON que devuelve? Sí, el servicio "Consumidor" empezará a fallar alegremente. Para que eso no pase usamos contratos, que nos permiten asegurarnos de que la interacción entre servicios sigue "en la misma onda".


¿Qué es Pact?

Pact — es un framework para pruebas contractuales que ayuda a garantizar que dos microservicios (o cliente/servidor) interactúan tal como se prometieron. Pact trabaja con el patrón "consumer-provider":

  • Consumidor (Consumer) crea un contrato que describe cómo espera que el provider se comporte.
  • Proveedor (Provider) verifica que su funcionalidad cumple con el contrato y actualiza el contrato si hace cambios.

Ejemplo de esquema simple de Pact:

[Servicio consumidor -> Genera el contrato -> El contrato se guarda -> Proveedor verifica el contrato]

Tarea: Implementemos pruebas contractuales

Para nuestro ejemplo tendremos:

  1. Consumidor — el microservicio CustomerService, que llama al API del servicio ProductService.
  2. Proveedor — el microservicio ProductService, que provee datos sobre los productos.

Objetivo: Escribir tests para CustomerService y comprobar que ProductService cumple el contrato.

Paso 1: Preparar el entorno

Para trabajar con Pact en Gradle añadiremos las siguientes dependencias:


dependencies {
    testImplementation 'au.com.dius.pact.provider:junit5:4.6.2'
    testImplementation 'au.com.dius.pact.consumer:junit5:4.6.2'
}

Ahora tenemos todo para trabajar con Pact tanto en el lado del consumidor como en el del proveedor.

Paso 2: Escribamos el contrato para el consumidor

Crearemos la clase de test CustomerServicePactTest, donde comprobaremos la interacción con ProductService.


import au.com.dius.pact.consumer.Pact;
import au.com.dius.pact.consumer.dsl.PactDslWithProvider;
import au.com.dius.pact.consumer.junit5.PactConsumerTestExt;
import au.com.dius.pact.consumer.junit5.PactTestFor;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

import java.util.Map;

import static org.junit.jupiter.api.Assertions.assertEquals;

@ExtendWith(PactConsumerTestExt.class)
public class CustomerServicePactTest {

    @Pact(consumer = "CustomerService", provider = "ProductService")
    public Map<String, Object> createPact(PactDslWithProvider builder) {
        return builder
                .given("Existe un producto con ID 1")
                .uponReceiving("petición para obtener información del producto")
                .path("/products/1")
                .method("GET")
                .willRespondWith()
                .status(200)
                .body("{\"id\": 1, \"name\": \"Laptop\", \"price\": 999.99}")
                .toPact();
    }

    @Test
    @PactTestFor(providerName = "ProductService", port = "8080")
    void testGetProduct() {
        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<String> response = restTemplate.getForEntity("http://localhost:8080/products/1", String.class);

        assertEquals(200, response.getStatusCodeValue());
        assertEquals("{\"id\": 1, \"name\": \"Laptop\", \"price\": 999.99}", response.getBody());
    }
}

Qué pasa aquí:

  1. En el método createPact describimos el contrato:
    • En la condición (given), que existe un producto con ID 1
    • Si el cliente solicita /products/1, devuelve un objeto JSON con los datos del producto
  2. En el test testGetProduct comprobamos que CustomerService envía correctamente la petición y recibe la respuesta esperada.

Este test genera un contrato, que se guarda como un archivo JSON.

Ejemplo de contrato:


{
  "provider": { "name": "ProductService" },
  "consumer": { "name": "CustomerService" },
  "interactions": [
    {
      "description": "petición para obtener información del producto",
      "request": {
        "method": "GET",
        "path": "/products/1"
      },
      "response": {
        "status": 200,
        "body": { "id": 1, "name": "Laptop", "price": 999.99 }
      }
    }
  ]
}

Paso 3: Verificamos el contrato en el lado del proveedor

Ahora vamos a ProductService. Aquí comprobamos que el API de este servicio cumple el contrato.

Crearemos la clase de test ProductServicePactTest:


import au.com.dius.pact.provider.junit5.PactVerificationContext;
import au.com.dius.pact.provider.junit5.PactVerificationInvocationContextProvider;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

@ExtendWith(PactVerificationInvocationContextProvider.class)
public class ProductServicePactTest {

    @BeforeEach
    void before(PactVerificationContext context) {
        context.setTarget(new HttpTestTarget("localhost", 8080));
    }

    @Test
    void pactVerificationTest(PactVerificationContext context) {
        context.verifyInteraction();
    }
}

Lo esencial aquí:

  • HttpTestTarget indica dónde está el API de nuestro proveedor (localhost:8080).
  • El método verifyInteraction carga el contrato que generamos para el consumidor y verifica que ProductService cumple con las interacciones descritas.

Paso 4: Ejecutemos todo junto

  1. Levanta ProductService en localhost:8080. Asegúrate de que devuelve los datos correctos para la petición /products/1.
  2. Primero ejecuta el test CustomerServicePactTest para generar el contrato.
  3. Luego ejecuta ProductServicePactTest para verificar el proveedor.

Manejo de errores y problemas típicos

  1. Modificación del API del proveedor: si el equipo de ProductService modifica el API, los tests del proveedor empezarán a fallar. Eso es una señal — toca actualizar el contrato y coordinar los cambios con el consumidor.
  2. Desajuste de datos en el contrato: si el proveedor devuelve campos adicionales, no siempre es un error, pero hay que hablarlo con el equipo del consumidor.
  3. Múltiples contratos: si ProductService atiende a varios consumidores, los contratos de cada uno deben verificarse por separado.

¿Para qué sirve todo esto en la práctica?

Las pruebas contractuales ayudan a detectar problemas de integración entre microservicios antes de que salgan a producción. Esto es especialmente importante en equipos que trabajan en servicios distintos, cuando cambios en un lado pueden "romper" al otro sin que nadie se dé cuenta.

Los contratos también encajan muy bien en sistemas de CI/CD: se pueden ejecutar los tests como parte del pipeline de CI/CD para automatizar la verificación de cambios.


¡Felicidades! Ahora sabes escribir pruebas contractuales con Pact. Es un paso importante hacia sistemas de microservicios más fiables y de calidad. ¡Que vivan las integraciones estables!

Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION