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

Patrón de contenedores singleton

A medida que aumenta el número de clases de prueba, iniciar contenedores para cada clase consume tiempo y recursos. El patrón de contenedores singleton inicia todos los contenedores requeridos una sola vez en una clase base común y los reutiliza en todas las pruebas de integración.

Definir la clase base

Crea una clase base abstracta que inicie los contenedores en un inicializador estático (static initializer):

package com.testcontainers.demo;

import org.testcontainers.postgresql.PostgreSQLContainer;
import org.testcontainers.kafka.ConfluentKafkaContainer;

public abstract class AbstractIntegrationTest {

   static PostgreSQLContainer postgres = new PostgreSQLContainer(
           "postgres:16-alpine");
   static ConfluentKafkaContainer kafka = new ConfluentKafkaContainer(
           "confluentinc/cp-kafka:7.8.0");

   static {
       postgres.start();
       kafka.start();
   }
}

Los contenedores se inician una vez cuando se carga la clase, y Testcontainers utiliza el contenedor Ryuk para eliminarlos después de que la JVM finalice.

Tip

En su lugar de iniciar los contenedores de forma secuencial, inícialos en paralelo usando Startables.deepStart(postgres, kafka).join();

Extender la clase base

Cada clase de prueba hereda de la clase base y reutiliza los mismos contenedores:

class ProductControllerTest extends AbstractIntegrationTest {

   ProductRepository productRepository;

   @BeforeEach
   void setUp() {
       productRepository = new ProductRepository(...);
       productRepository.deleteAll();
   }

   @Test
   void shouldGetAllProducts() {
       // lógica de prueba usando el contenedor postgres compartido
   }
}

Evitar un error de configuración común

Un error común es combinar contenedores singleton con las anotaciones @Testcontainers y @Container:

// NO HAGAS ESTO — los contenedores se detendrán después de cada clase de prueba
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
public abstract class AbstractIntegrationTest {

   @Container
   static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>(
           DockerImageName.parse("postgres:16-alpine"));

   @DynamicPropertySource
   static void configureProperties(DynamicPropertyRegistry registry) {
       registry.add("spring.datasource.url", postgres::getJdbcUrl);
       registry.add("spring.datasource.username", postgres::getUsername);
       registry.add("spring.datasource.password", postgres::getPassword);
   }
}

La extensión @Testcontainers detiene los contenedores al final de cada clase de prueba. Las clases de prueba subsiguientes reutilizan el contexto de Spring almacenado en caché, pero los contenedores ya están detenidos, lo que provoca fallos de conexión.

En su lugar, utiliza un inicializador estático o @BeforeAll para iniciar los contenedores, sin las anotaciones @Testcontainers y @Container.

Resumen

  • Utiliza callbacks del ciclo de vida de JUnit 5 (@BeforeAll/@AfterAll) para obtener un control explícito sobre el inicio y la detención de los contenedores.
  • Utiliza anotaciones de extensión (@Testcontainers/@Container) para reducir el código repetitivo (boilerplate) en clases de prueba individuales.
  • Utiliza el patrón de contenedores singleton (inicializador estático en una clase base) para compartir contenedores a través de múltiples clases de prueba.
  • No mezcles contenedores singleton con las anotaciones @Testcontainers/@Container.

Lecturas recomendadas