Тесты с @SpringBootTest
В учебнике вы писали тесты JUnit для обычных классов. Сервис блога так не проверить: ему нужны репозитории, база и Clock. Аннотация @SpringBootTest поднимает в тесте весь контейнер Spring — так же, как при запуске, только без открытия порта. Бины можно получить прямо в тестовый класс.
Что уже есть
Генератор положил в проект первый тест, а в pom.xml — тестовые стартеры (spring-boot-starter-webmvc-test и другие с -test), в которых есть JUnit 5, AssertJ и Mockito.
// src/test/java/com/example/blog/BlogApplicationTests.java
@SpringBootTest
@ActiveProfiles("test")
class BlogApplicationTests {
@Test
void contextLoads() {
}
}Пустой тест не так бесполезен: он проходит, только если приложение поднимается целиком — все бины созданы, настройки прочитаны, миграции применились.
Своя база для тестов
Тесты не должны трогать файл data/blog.mv.db. Создадим профиль test с базой в памяти:
# src/test/resources/application-test.yaml
spring:
datasource:
url: jdbc:h2:mem:blog-test
jpa:
show-sql: false@ActiveProfiles("test") включает этот профиль. База mem: живёт, пока работает тестовый процесс; Flyway создаст в ней таблицы при старте.
Тест сервиса
// src/test/java/com/example/blog/PostServiceTests.java
package com.example.blog;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.transaction.annotation.Transactional;
@SpringBootTest
@ActiveProfiles("test")
@Transactional
class PostServiceTests {
@Autowired
private PostService postService;
@Autowired
private UserRepository users;
@Test
void createsPostWithAuthor() {
users.save(new User("anna", "{noop}changeme"));
PostResponse post = postService.create(new NewPostRequest("Привет", "Текст"), "anna");
assertThat(post.id()).isNotNull();
assertThat(post.author()).isEqualTo("anna");
}
@Test
void throwsWhenPostIsMissing() {
assertThatThrownBy(() -> postService.findById(999L))
.isInstanceOf(PostNotFoundException.class)
.hasMessage("Статья 999 не найдена");
}
}В тестах бины получают через @Autowired на поле — конструктор тестового класса вызывает JUnit, а не Spring. {noop} — приставка «пароль без шифрования», для тестовых данных это допустимо. @Transactional над тестовым классом откатывает транзакцию после каждого теста, и база снова пуста.
Запуск
./mvnw test[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.049 s -- in com.example.blog.PostServiceTests
[INFO] Tests run: 12, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESSПервый тест в прогоне идёт секунд десять — Spring поднимает приложение. Остальные классы с теми же настройками используют уже готовый контейнер, поэтому быстры. Команда ./mvnw package тоже запускает тесты и не соберёт JAR, если они упали.
Частая ошибка
Без @Transactional данные одного теста остаются в базе для следующих. Тест, который в @BeforeEach создаёт Анну, упадёт со второго раза:
[ERROR] Tests run: 4, Failures: 0, Errors: 3, Skipped: 0 <<< FAILURE! -- in com.example.blog.PostControllerTests
Unique index or primary key violation: "PUBLIC.CONSTRAINT_4D INDEX PUBLIC.CONSTRAINT_4D_INDEX_6 ON PUBLIC.USERS(USERNAME NULLS FIRST) VALUES ( /* 1 */ 'anna' )"Пользователь anna уже есть, а имя уникально. Добавьте @Transactional к тестовому классу — и каждый тест начнётся с чистой базы.