Gymterview
middle

Что такое Testcontainers и зачем они нужны?

Testcontainers — библиотека, позволяющая запускать Docker-контейнеры прямо из тестов. Она решает проблему расхождения между тестовой (H2) и боевой (PostgreSQL, MySQL) базами данных, обеспечивая тестирование на реальной СУБД.

Зачем нужны

  • H2 не поддерживает все возможности PostgreSQL (jsonb, оконные функции, специфичные типы)
  • Тесты на реальной БД дают большую уверенность в коде
  • Контейнеры автоматически создаются и уничтожаются

Подключение зависимостей

Пример
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>testcontainers</artifactId>
    <scope>test</scope>
</dependency>
<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>

Пример с PostgreSQL

Пример кода
@SpringBootTest
@Testcontainers
class UserServiceIntegrationTest {

    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")
        .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 UserService userService;

    @Test
    void shouldSaveAndRetrieveUser() {
        User user = userService.create(new UserDto("Иван", "ivan@example.com"));

        assertNotNull(user.getId());
        assertEquals("Иван", userService.findById(user.getId()).getName());
    }
}

Базовый абстрактный класс для переиспользования контейнера

Пример кода
@Testcontainers
public abstract class AbstractPostgresTest {

    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")
        .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);
    }
}

@SpringBootTest
class OrderServiceTest extends AbstractPostgresTest {
    // Переиспользует контейнер PostgreSQL
}

На собеседовании: интервьюер хочет услышать, зачем Testcontainers нужны вместо H2 и как подключить контейнер через @DynamicPropertySource. Частая ошибка — не упомянуть абстрактный базовый класс для переиспользования контейнера между тестами.