CodeGym /Cursos /Módulo 5. Spring /Lección 266: Introducción a las pruebas de contrato (Pact...

Lección 266: Introducción a las pruebas de contrato (Pact)

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

Vamos al grano. Cuando los microservicios interactúan entre sí, se parecen a dos desarrolladores que se han puesto de acuerdo: "¡Te voy a enviar un archivo en formato JSON con estos campos!". En las pruebas de contrato ese "acuerdo" se llama contrato. La idea es que las interacciones entre servicios se verifiquen contra condiciones predefinidas para asegurarse de que nadie rompa el "acuerdo" por accidente.

Ejemplo: Imagina que trabajas en el microservicio "Cliente", que hace peticiones HTTP al microservicio "Productos". "Productos" responde con un JSON con la información del producto. Puedes escribir pruebas de contrato para garantizar que "Productos" siempre devuelva un JSON correcto que cumpla las expectativas de "Cliente".

¿Por qué es importante?

En una arquitectura de microservicios, donde cada servicio es independiente pero se comunica con otros, actualizar una parte del sistema puede romper fácilmente otra. Por ejemplo, si el equipo de "Productos" cambia de repente la estructura del JSON de respuesta, el servicio "Cliente" empezará a fallar. Las pruebas de contrato ayudan a evitar estas situaciones:

  • Garantiza la estabilidad de las interacciones entre servicios.
  • Ahorra tiempo, porque los problemas de integración se detectan en etapas tempranas del desarrollo.
  • Reduce el número de comprobaciones manuales, porque las interacciones se testean automáticamente.

Diferencia entre pruebas de integración y pruebas de contrato

Las pruebas de integración verifican la interacción de varios componentes, mientras que las pruebas de contrato se centran en la correspondencia exacta de los datos de entrada y salida esperados entre dos servicios. Las pruebas de contrato son útiles porque permiten probar la interacción en un entorno aislado, sin tener que levantar ambos servicios.


Fundamentos de trabajo con Pact

Pact es una herramienta popular para pruebas de contrato. Actúa como intermediario entre dos partes:

  • Consumer (consumer) — el servicio que envía las peticiones.
  • Provider (provider) — el servicio que responde a las peticiones.

Pact permite crear y verificar contratos entre estas partes.

Principio básico de funcionamiento de Pact

  1. Creación del contrato: el consumer crea un contrato que describe qué peticiones envía y qué respuestas espera del provider.
  2. Verificación del contrato: el provider verifica el contrato para asegurarse de que puede responder correctamente a las peticiones del consumer.

Ejemplo del flujo de trabajo:

  1. Creación del contrato: El servicio "Cliente" genera un contrato donde se indica:
    • Enviaré una petición GET a /products/1
    • Espero en la respuesta un JSON con los campos id, name, price
  2. Este contrato se pasa al servicio "Productos".
  3. El servicio "Productos" usa Pact para verificar que puede enviar ese JSON.

Ejemplo de uso de Pact para microservicios

Pongamos manos a la obra. Imagina que tenemos dos microservicios:

  • Consumer (consumidor): el servicio "Cliente", que solicita información de productos.
  • Provider (proveedor): el servicio "Productos", que devuelve la información de los productos.

Paso 1: Creación del contrato en el lado del consumidor

Empezamos con "Cliente". En este servicio describimos qué petición y respuesta esperamos de "Productos".

Antes de empezar, añadimos la librería Pact en nuestro build.gradle:

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

Escribir el test usando Pact


@PactTestFor(providerName = "ProductService")
public class ProductConsumerContractTest {

    @Pact(consumer = "CustomerService")
    public RequestResponsePact createPact(PactDslWithProvider builder) {
        return builder
                .given("Product with ID 1 exists")
                .uponReceiving("A request for product with ID 1")
                .path("/products/1")
                .method("GET")
                .willRespondWith()
                .status(200)
                .body("{\"id\": 1, \"name\": \"Laptop\", \"price\": 1200.00}")
                .toPact();
    }

    @Test
    @PactTestFor(pactMethod = "createPact")
    public void testConsumerBehaviour(MockServer mockServer) {
        // Usamos mockServer para emular la API de "Productos"
        String response = new RestTemplate().getForObject(mockServer.getUrl() + "/products/1", String.class);

        // Comprobamos que la respuesta coincide con lo esperado
        assertEquals("{\"id\": 1, \"name\": \"Laptop\", \"price\": 1200.00}", response);
    }
}

Explicación:

  • Crearemos un contrato usando Pact.
  • En el contrato se indica que el consumidor (CustomerService) hace una petición GET a /products/1 y espera un JSON específico en la respuesta.
  • Pact levanta un MockServer para probar el comportamiento del consumidor.

Paso 2: Verificación del contrato en el lado del proveedor

Ahora pasamos el contrato al servicio "Productos" y comprobamos que cumple con lo esperado.

Añadimos Pact en el servicio proveedor:

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

Verificación del contrato


@Provider("ProductService")
@PactBroker(host = "localhost", port = "9292")  // Broker de Pact para almacenar los contratos
public class ProductProviderContractTest {

    @TestTemplate
    @ExtendWith(PactVerificationInvocationContextProvider.class)
    public void validatePacts(PactVerificationContext context) {
        context.verifyInteraction();
    }

    @State("Product with ID 1 exists")
    public void productExists() {
        // Configuramos la base de datos de pruebas o mocks para el estado "Product with ID 1 exists"
    }
}

Explicación:

  • Usamos Pact para verificar el contrato.
  • En el método productExists definimos el estado inicial (por ejemplo, añadimos un registro en la base de datos).
  • Pact verifica automáticamente que el servicio "Productos" cumple con el contrato.

Ventajas de usar Pact

  1. Detección temprana de problemas: los problemas de integración entre consumidor y proveedor se vuelven visibles de inmediato.
  2. Aislamiento de las pruebas: consumidor y proveedor se prueban de forma independiente.
  3. Documentación de las interacciones: los contratos sirven como auto-documentación del API.

Conclusión

Las pruebas de contrato son el superhéroe de las interacciones en microservicios. Una herramienta como Pact ayuda a tus equipos a dormir tranquilos sabiendo que las actualizaciones de un servicio no romperán otros. En las siguientes lecciones nos meteremos más en el uso práctico de Pact y escribiremos nuestros primeros tests de contrato para interacciones entre microservicios.

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