Aller au contenu

Testcontainers

Contexte

Les tests d'intégration nécessitent des dépendances externes (base de données, broker Kafka, cache Redis). Les approches classiques posent problème :

  • mocks — ne testent pas le comportement réel
  • base partagée — conflits entre développeurs, état imprévisible
  • H2 en mémoire — comportement différent de PostgreSQL en production

Testcontainers résout ce problème en démarrant de vrais containers Docker pendant les tests.


Principe

Testcontainers lance des containers Docker jetables pour chaque suite de tests :

Test démarre → Container PostgreSQL créé → Tests exécutés → Container détruit

Les tests tournent contre de vraies instances, identiques à la production.


Exemple avec Spring Boot et PostgreSQL

Dépendance

<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>postgresql</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>junit-jupiter</artifactId>
    <scope>test</scope>
</dependency>

Configuration

@SpringBootTest
@Testcontainers
class UserRepositoryTest {

    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16")
            .withDatabaseName("testdb")
            .withUsername("test")
            .withPassword("test");

    @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);
    }

    @Autowired
    private UserRepository userRepository;

    @Test
    void shouldSaveAndFindUser() {
        User user = new User("Ali", "ali@test.com");
        userRepository.save(user);

        Optional<User> found = userRepository.findByEmail("ali@test.com");
        assertThat(found).isPresent();
        assertThat(found.get().getName()).isEqualTo("Ali");
    }
}

Exemple avec Kafka

@Container
static KafkaContainer kafka = new KafkaContainer(
    DockerImageName.parse("confluentinc/cp-kafka:7.5.0")
);

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

Pattern : base de test partagée

Pour accélérer les tests, on peut partager un container entre toutes les classes de test :

public abstract class AbstractIntegrationTest {

    static final PostgreSQLContainer<?> postgres;

    static {
        postgres = new PostgreSQLContainer<>("postgres:16")
                .withDatabaseName("testdb")
                .withUsername("test")
                .withPassword("test");
        postgres.start();
    }

    @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);
    }
}

Les classes de test héritent de AbstractIntegrationTest — un seul container pour toute la suite.


Containers disponibles

Module Image Usage
postgresql postgres:16 Base relationnelle
kafka confluentinc/cp-kafka Broker Kafka
mongodb mongo:7 Base document
localstack localstack/localstack AWS (S3, SQS, DynamoDB)
elasticsearch elasticsearch:8 Moteur de recherche

Avantages

  • tests contre des vraies dépendances, pas des mocks
  • environnement identique pour chaque développeur
  • pas de pollution entre les tests
  • fonctionne en CI/CD (Docker-in-Docker ou Docker socket)

Inconvénients

  • nécessite Docker sur la machine de développement
  • plus lent que des tests unitaires purs
  • consomme des ressources (CPU, RAM, disque)

À retenir

  • Testcontainers remplace les mocks et H2 par de vraies instances
  • utiliser @DynamicPropertySource avec Spring Boot pour injecter les URLs
  • partager les containers entre les tests pour la performance
  • indispensable pour les tests d'intégration en architecture microservices

Voir aussi