Compartir comentarios
Las respuestas se generan en base a la documentación.

Escribe pruebas con Testcontainers

Tabla de contenidos

Para probar el listener de Kafka, necesitas un broker de Kafka en ejecución y una base de datos MySQL, además de un contexto de Spring iniciado. Testcontainers levanta ambos servicios en contenedores de Docker y @DynamicPropertySource los conecta a Spring.

Escribe la prueba

Crea ProductPriceChangedEventHandlerTest.java:

package com.testcontainers.demo;

import static java.util.concurrent.TimeUnit.SECONDS;
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;

import java.math.BigDecimal;
import java.time.Duration;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.test.context.TestPropertySource;
import org.testcontainers.kafka.ConfluentKafkaContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

@SpringBootTest
@TestPropertySource(
  properties = {
    "spring.kafka.consumer.auto-offset-reset=earliest",
    "spring.datasource.url=jdbc:tc:mysql:8.0.32:///db",
  }
)
@Testcontainers
class ProductPriceChangedEventHandlerTest {

  @Container
  static final ConfluentKafkaContainer kafka =
    new ConfluentKafkaContainer("confluentinc/cp-kafka:7.8.0");

  @DynamicPropertySource
  static void overrideProperties(DynamicPropertyRegistry registry) {
    registry.add("spring.kafka.bootstrap-servers", kafka::getBootstrapServers);
  }

  @Autowired
  private KafkaTemplate<String, Object> kafkaTemplate;

  @Autowired
  private ProductRepository productRepository;

  @BeforeEach
  void setUp() {
    Product product = new Product(null, "P100", "Product One", BigDecimal.TEN);
    productRepository.save(product);
  }

  @Test
  void shouldHandleProductPriceChangedEvent() {
    ProductPriceChangedEvent event = new ProductPriceChangedEvent(
      "P100",
      new BigDecimal("14.50")
    );

    kafkaTemplate.send("product-price-changes", event.productCode(), event);

    await()
      .pollInterval(Duration.ofSeconds(3))
      .atMost(10, SECONDS)
      .untilAsserted(() -> {
        Optional<Product> optionalProduct = productRepository.findByCode(
          "P100"
        );
        assertThat(optionalProduct).isPresent();
        assertThat(optionalProduct.get().getCode()).isEqualTo("P100");
        assertThat(optionalProduct.get().getPrice())
          .isEqualTo(new BigDecimal("14.50"));
      });
  }
}

Esto es lo que hace la prueba:

  • @SpringBootTest inicia el contexto completo de la aplicación Spring.
  • La URL JDBC especial de Testcontainers (jdbc:tc:mysql:8.0.32:///db) en @TestPropertySource levanta un contenedor de MySQL y lo configura como el origen de datos (datasource) automáticamente.
  • @Testcontainers y @Container gestionan el ciclo de vida del contenedor de Kafka. @DynamicPropertySource registra los servidores de bootstrap de Kafka con Spring para que el productor y el consumidor se conecten al contenedor de prueba.
  • @BeforeEach crea un registro de Product en la base de datos antes de cada prueba.
  • La prueba envía un ProductPriceChangedEvent al tema product-price-changes utilizando KafkaTemplate. Spring Boot convierte el objeto a JSON utilizando JsonSerializer.
  • Debido a que el procesamiento de mensajes de Kafka es asíncrono, la prueba utiliza Awaitility para realizar consultas (polling) cada 3 segundos (hasta un máximo de 10 segundos) hasta que el precio del producto en la base de datos coincida con el valor esperado.
  • La propiedad spring.kafka.consumer.auto-offset-reset se establece en earliest para que el listener consuma los mensajes incluso si se envían al tema antes de que el listener esté listo. Esta configuración es útil cuando se ejecutan pruebas.