# 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):

```java
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](https://github.com/testcontainers/moby-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:

```java
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`:

```java
// 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

- [Guía de inicio rápido de Testcontainers JUnit 5](https://java.testcontainers.org/quickstart/junit_5_quickstart/)
- [Patrón de contenedores singleton de Testcontainers](https://java.testcontainers.org/test_framework_integration/manual_lifecycle_control/#singleton-containers)
- [Probar una API REST de Spring Boot con Testcontainers](/guides/testcontainers-java-spring-boot-rest-api/)

