Что делать если нет возможности локально устанавливать базу данных для использования ее с Unit-тестами
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Тестирование без локальной базы данных
Если у вас нет возможности установить реальную базу данных локально, есть несколько отличных решений для Unit и Integration тестирования. Вот полный спектр подходов от простого к сложному.
1. Встроенные (Embedded) базы данных
Это памятные БД, которые запускаются прямо в рамках теста, без необходимости отдельной установки.
H2 Database (самый популярный)
<!-- pom.xml -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
# application-test.yml
spring:
datasource:
url: jdbc:h2:mem:testdb
driverClassName: org.h2.Driver
username: sa
password:
jpa:
database-platform: org.hibernate.dialect.H2Dialect
hibernate:
ddl-auto: create-drop # Создаёт и удаляет схему для каждого теста
@SpringBootTest
@ActiveProfiles("test")
class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@BeforeEach
void setUp() {
// База данных создаётся для каждого теста
}
@Test
void testSaveUser() {
User user = new User("John", "john@example.com");
User saved = userRepository.save(user);
assertNotNull(saved.getId());
assertEquals("John", saved.getName());
}
@AfterEach
void tearDown() {
// База данных автоматически очищается
}
}
Преимущества:
- ✅ Работает в памяти (очень быстро)
- ✅ Не нужна установка
- ✅ Автоматически очищается
- ✅ Полностью совместима с SQL
Недостатки:
- ❌ Отличается от production БД (синтаксис, функции)
- ❌ Может скрывать проблемы специфичные для PostgreSQL/MySQL
Derby (встроенная Apache база)
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
<scope>test</scope>
</dependency>
spring:
datasource:
url: jdbc:derby:memory:testdb;create=true
driverClassName: org.apache.derby.jdbc.EmbeddedDriver
jpa:
database-platform: org.hibernate.dialect.DerbyDialect
2. TestContainers - лучший вариант для интеграционных тестов
Это Docker контейнеры, запускаемые во время тестирования. Дают вам полностью идентичную production среду.
Установка
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>1.19.0</version>
<scope>test</scope>
</dependency>
<!-- PostgreSQL контейнер -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>1.19.0</version>
<scope>test</scope>
</dependency>
<!-- MySQL контейнер -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>mysql</artifactId>
<version>1.19.0</version>
<scope>test</scope>
</dependency>
Пример с PostgreSQL
@SpringBootTest
class UserRepositoryIT { // IT = Integration Test
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")
.withDatabaseName("testdb")
.withUsername("test")
.withPassword("password");
@Autowired
private UserRepository userRepository;
@DynamicPropertySource
static void configureTestDatabase(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
@Test
void testUserOperations() {
User user = new User("Alice", "alice@example.com");
User saved = userRepository.save(user);
User found = userRepository.findById(saved.getId()).orElse(null);
assertNotNull(found);
assertEquals("Alice", found.getName());
}
}
Spring Boot 3.1+ (более удобно)
@SpringBootTest
class UserRepositoryIT {
static {
// Автоматически запускает контейнер
Testcontainers.checks();
}
@ServiceConnection
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15");
@Autowired
private UserRepository userRepository;
@Test
void testSaveUser() {
User user = new User("Bob", "bob@example.com");
User saved = userRepository.save(user);
assertNotNull(saved.getId());
}
}
Преимущества TestContainers:
- ✅ Полностью идентична production БД
- ✅ Работает с Docker (распространено везде)
- ✅ Поддерживает все популярные БД (PostgreSQL, MySQL, MongoDB и т.д.)
- ✅ Автоматическая очистка после теста
- ✅ Легко с параметрами (версия БД, переменные среды)
Недостатки:
- ❌ Нужен Docker установлен
- ❌ Медленнее встроенных БД (нужно создать контейнер)
- ❌ Для быстрых unit-тестов может быть оверкилл
3. Мокирование БД слоя (Repository Mock)
Вместо реальной БД мокируем Repository.
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User getUserById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException(id));
}
public User createUser(String name, String email) {
User user = new User(name, email);
return userRepository.save(user);
}
}
// Тест с мокированием
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
void testGetUserById() {
// Arrange
Long userId = 1L;
User mockUser = new User(userId, "John", "john@example.com");
when(userRepository.findById(userId))
.thenReturn(Optional.of(mockUser));
// Act
User result = userService.getUserById(userId);
// Assert
assertEquals("John", result.getName());
verify(userRepository).findById(userId);
}
@Test
void testGetUserByIdNotFound() {
// Arrange
Long userId = 999L;
when(userRepository.findById(userId))
.thenReturn(Optional.empty());
// Act & Assert
assertThrows(UserNotFoundException.class,
() -> userService.getUserById(userId)
);
}
}
Когда использовать:
- ✅ Unit-тесты business логики
- ✅ Изолированное тестирование сервисов
- ✅ Быстрые тесты
Когда НЕ использовать:
- ❌ Интеграционные тесты
- ❌ Тестирование запросов в БД
- ❌ Тестирование транзакций
4. In-Memory стратегия - комбинированный подход
Используем встроенную БД для быстрых интеграционных тестов:
@SpringBootTest
@ActiveProfiles("test") // Использует application-test.yml
class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@BeforeEach
void setUp() {
userRepository.deleteAll(); // Очищаем перед каждым тестом
}
@Test
void testFindByEmail() {
// Arrange
User user = new User("john@example.com", "John");
userRepository.save(user);
// Act
User found = userRepository.findByEmail("john@example.com");
// Assert
assertNotNull(found);
assertEquals("John", found.getName());
}
}
5. Docker Compose для локальной разработки
Если у вас есть Docker, но не хочется использовать TestContainers:
# docker-compose.yml
version: 3.8
services:
postgres:
image: postgres:15
environment:
POSTGRES_USER: testuser
POSTGRES_PASSWORD: password
POSTGRES_DB: testdb
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
# Запускаем
docker-compose up -d
# Запускаем тесты
mvn test
# Останавливаем
docker-compose down
6. Рекомендуемая стратегия для разных сценариев
Сценарий 1: Запуск тестов в CI/CD (GitHub Actions, GitLab CI, Jenkins)
// Используем H2 или TestContainers (в CI уже есть Docker)
@SpringBootTest
@ActiveProfiles("test") // Профиль для H2
class IntegrationTest {
// Тесты
}
Сценарий 2: Локальная разработка
# application-dev.yml - реальная БД (Docker)
spring:
datasource:
url: jdbc:postgresql://localhost:5432/mydb
username: dev
password: dev
# application-test.yml - встроенная БД
spring:
datasource:
url: jdbc:h2:mem:testdb
Сценарий 3: Быстрые unit-тесты
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository; // Мокируем, не нужна БД
@InjectMocks
private UserService userService;
// Быстрые тесты
}
Сценарий 4: Полные интеграционные тесты
@SpringBootTest
class FullIntegrationTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>();
@Autowired
private UserRepository userRepository;
@Autowired
private UserService userService;
// Тесты с реальной логикой БД
}
7. Структура проекта
src/
├── main/
│ ├── java/com/example/
│ │ ├── domain/ (бизнес логика, не зависит от БД)
│ │ ├── service/ (сервисы)
│ │ └── repository/ (доступ в БД)
│ └── resources/
│ ├── application.yml (production)
│ ├── application-dev.yml (разработка)
│ └── application-test.yml (тесты)
└── test/
└── java/com/example/
├── service/ (unit-тесты с мокированием)
├── repository/ (интеграционные тесты с БД)
└── integration/ (end-to-end тесты)
Итоговые рекомендации
| Ситуация | Решение |
|---|---|
| Быстрые unit-тесты | Мокирование (Mockito) |
| Интеграционные тесты, нет Docker | H2 встроенная БД |
| Интеграционные тесты, есть Docker | TestContainers |
| Локальная разработка без Docker | Docker Compose |
| CI/CD pipeline | TestContainers или H2 |
| Production-like тесты | TestContainers с реальной БД |
Самый простой старт:
<!-- Добавьте в pom.xml -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
# application-test.yml
spring:
datasource:
url: jdbc:h2:mem:testdb
driverClassName: org.h2.Driver
jpa:
database-platform: org.hibernate.dialect.H2Dialect
hibernate:
ddl-auto: create-drop
Теперь вы можете писать интеграционные тесты без установки реальной БД!