Как правильно тестировать Spring Boot приложение с Testcontainers и несколькими профилями БД?

Проблема:

Я разрабатываю Spring Boot приложение с поддержкой разных СУБД (PostgreSQL, H2 для тестов). Хочу настроить интеграционные тесты с Testcontainers, но столкнулся с проблемами:

  1. При запуске тестов с @DataJpaTest контейнер PostgreSQL не инициализируется
  2. Нужно поддерживать два режима тестирования:
    • Быстрые тесты на H2 (для изолированных unit-тестов)
    • Полноценные тесты на PostgreSQL в контейнере (для интеграционных тестов)

Что я пробовал:

Добавлял @Testcontainers и @Container (как в документации)

Менял @DataJpaTest на @SpringBootTest - тогда работает, но тесты выполняются дольше.

Пробовал разные комбинации @AutoConfigureTestDatabase

Текущая реализация:

// Тестовый класс
@Testcontainers
@DataJpaTest
@AutoConfigureTestDatabase(replace = Replace.NONE)
public class UserRepositoryTest {
    
    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15");
    
    @DynamicPropertySource
    static void configureProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", postgres::getJdbcUrl);
        // ... остальные параметры
    }
    
    @Test
    void shouldSaveUser() {
        // ... тест падает с NPE
    }
}

Ошибка:

java.lang.NullPointerException: Cannot invoke "org.testcontainers.containers.PostgreSQLContainer.getJdbcUrl()" because "com.example.UserRepositoryTest.postgres" is null


Ответы (2 шт):

Автор решения: Roman C

Если вы собираетесь тестировать контейнер, то его надо запускать при выполнении тестов. Статическая инициализация поля ни к чему не приводит, так как переменная остаётся не инициализированной, что приводит к NPE.

Попробуйте добавить инициализацию контейнера в тестируемый метод.

    @Test
    void shouldSaveUser() {
      try (PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>(PostgreSQLTestImages.POSTGRES_TEST_IMAGE)) {
         postgres.start();
         String jdbcUrl = postgres.getJdbcUrl();
         assertNotNull(jdbcUrl);       
      }
    }
→ Ссылка
Автор решения: Василий Волгин

Предлагаю перейти на @SpringBootTest для интеграционных тестов. типо так:

@SpringBootTest
@Testcontainers
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class UserRepositoryIT {

    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15");

    @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);
        registry.add("spring.datasource.driver-class-name", postgres::getDriverClassName);
    }

    @Autowired
    private UserRepository userRepository;

    @Test
    void shouldSaveUser() {
        var user = new User("vasya");
        userRepository.save(user);
        assertNotNull(user.getId());
    }
}
→ Ссылка