Как организовать Unit-тесты с подключением к базе данных?

«Как организовать Unit-тесты с подключением к базе данных?» — вопрос из категории Тестирование, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Юнит-тесты с БД — это, строго говоря, интеграционные тесты. Для их изоляции и воспроизводимости используют следующие подходы:

1. In-memory база данных (H2, HSQLDB)

  • Быстрая настройка, не требует внешних зависимостей.
  • Может отличаться диалектом SQL от продакшн-БД.
    @Test
    public void testWithH2() {
    DataSource ds = new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .addScript("classpath:schema.sql")
            .addScript("classpath:test-data.sql")
            .build();
    // Использование ds в репозитории...
    }

2. Testcontainers (рекомендуемый для интеграционных тестов)

  • Запускает реальную БД (PostgreSQL, MySQL) в Docker-контейнере.
  • Гарантирует полную совместимость, но работает медленнее.

    @Testcontainers
    class RepositoryTest {
    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15");
    
    @Test
    void test() {
        String jdbcUrl = postgres.getJdbcUrl();
        // Подключение и тестирование...
    }
    }

3. Транзакции с откатом (Rollback)

  • Используется в Spring-приложениях для очистки состояния после каждого теста.
    @SpringBootTest
    @Transactional
    @Rollback
    class ServiceTest {
    @Test
    void testInTransaction() {
        // Изменения в БД будут откачены после теста
    }
    }

Ключевые принципы:

  • Изоляция: Каждый тест должен начинаться с чистого состояния.
  • Скорость: Такие тесты выполняются медленнее, их следует отделять от чистых юнит-тестов (например, аннотацией @IntegrationTest).
  • Идемпотентность: Результат теста не должен зависеть от порядка выполнения.