← Назад к вопросам
Что такое Extension в Spring Boot?
2.0 Middle🔥 181 комментариев
#Stream API и функциональное программирование#Многопоточность
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Extensions в Spring Boot и JUnit 5: расширяемость фреймворка
Extensions (расширения) — это механизм в JUnit 5 для расширения функциональности фреймворка. Spring Boot активно использует Extensions для интеграции с контейнером Spring в тестах. Это современная замена для Rules из JUnit 4.
Основные концепции
Extension — это интерфейс, который позволяет перехватывать и модифицировать жизненный цикл тестов:
// Самые распространённые типы Extensions:
// 1. BeforeEachCallback - выполнить перед каждым тестом
// 2. AfterEachCallback - выполнить после каждого теста
// 3. BeforeAllCallback - выполнить один раз перед всеми тестами
// 4. AfterAllCallback - выполнить один раз после всех тестов
// 5. ParameterResolver - предоставить параметры для методов
// 6. TestInstancePreProcessor - модифицировать экземпляр теста
// и другие...
Spring Boot Extension
Dля тестов Spring Boot используется @SpringBootTest аннотация, которая регистрирует SpringExtension:
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
// Способ 1: явная регистрация
@SpringBootTest
class MyServiceTest {
@Test
void testSomething() {
// Контекст Spring уже загружен
}
}
// Способ 2: явно регистрируем расширение
@ExtendWith(SpringExtension.class)
class MyServiceTestExplicit {
@Test
void testSomething() {
// Контекст Spring загружен через расширение
}
}
Практический пример: создание собственного Extension
1. Логирование тестов
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LoggingExtension implements BeforeEachCallback, AfterEachCallback {
private static final Logger logger = LoggerFactory.getLogger(LoggingExtension.class);
@Override
public void beforeEach(ExtensionContext context) throws Exception {
String testName = context.getDisplayName();
logger.info("Запуск теста: {}", testName);
}
@Override
public void afterEach(ExtensionContext context) throws Exception {
String testName = context.getDisplayName();
logger.info("Завершён тест: {}", testName);
}
}
// Использование:
@ExtendWith(LoggingExtension.class)
class UserServiceTest {
@Test
void testCreateUser() {
// Будет залогировано:
// INFO: Запуск теста: testCreateUser
// ... выполнение теста ...
// INFO: Завершён тест: testCreateUser
}
}
2. Extension для управления тестовыми данными
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.junit.jupiter.SpringExtension;
public class DatabaseCleanupExtension implements BeforeEachCallback {
@Override
public void beforeEach(ExtensionContext context) throws Exception {
// Получаем Spring контекст из расширения
ApplicationContext appContext = SpringExtension
.getApplicationContext(context);
// Получаем бин репозитория
UserRepository userRepository = appContext
.getBean(UserRepository.class);
// Очищаем БД перед каждым тестом
userRepository.deleteAll();
}
}
// Использование:
@SpringBootTest
@ExtendWith(DatabaseCleanupExtension.class)
class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Test
void testSaveUser() {
// БД уже очищена благодаря расширению
User user = new User("John", "john@example.com");
userRepository.save(user);
assertEquals(1, userRepository.count());
}
}
3. Extension для предоставления тестовых данных (ParameterResolver)
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolver;
import java.lang.reflect.Parameter;
public class TestUserProviderExtension implements ParameterResolver {
@Override
public boolean supportsParameter(ParameterContext parameterContext,
ExtensionContext extensionContext) {
// Проверяем, является ли параметр типом TestUser
Parameter parameter = parameterContext.getParameter();
return parameter.getType() == TestUser.class;
}
@Override
public Object resolveParameter(ParameterContext parameterContext,
ExtensionContext extensionContext) {
// Создаём и возвращаем тестового пользователя
return new TestUser("testuser", "test@example.com", "password123");
}
}
public class TestUser {
public final String username;
public final String email;
public final String password;
public TestUser(String username, String email, String password) {
this.username = username;
this.email = email;
this.password = password;
}
}
// Использование:
@ExtendWith(TestUserProviderExtension.class)
class AuthenticationTest {
@Test
void testUserLogin(TestUser testUser) {
// testUser автоматически предоставлен расширением
assertEquals("testuser", testUser.username);
}
}
Spring Boot встроенные Extensions
1. @SpringBootTest (содержит SpringExtension)
@SpringBootTest
class ApplicationTest {
@Autowired
private ApplicationContext context;
@Test
void contextLoads() {
assertNotNull(context);
}
}
2. @DataJpaTest (для тестирования репозиториев)
@DataJpaTest
class UserRepositoryTest {
@Autowired
private TestEntityManager entityManager;
@Autowired
private UserRepository userRepository;
@Test
void testSaveUser() {
User user = new User("John");
entityManager.persistAndFlush(user);
User found = userRepository.findById(user.getId()).orElse(null);
assertNotNull(found);
}
}
3. @WebMvcTest (для тестирования контроллеров)
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Test
void testGetUser() throws Exception {
User user = new User("John");
when(userService.findById(1L)).thenReturn(user);
mockMvc.perform(get("/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("John"));
}
}
Сложный пример: Extension с аннотацией
import org.junit.jupiter.api.extension.ExtendWith;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
// Создаём собственную аннотацию
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(TransactionalExtension.class)
public @interface Transactional {
// Это аннотация автоматически регистрирует расширение
}
// Само расширение
public class TransactionalExtension implements BeforeEachCallback, AfterEachCallback {
private TransactionStatus transactionStatus;
@Override
public void beforeEach(ExtensionContext context) throws Exception {
// Начинаем транзакцию
ApplicationContext appContext = SpringExtension.getApplicationContext(context);
PlatformTransactionManager txManager =
appContext.getBean(PlatformTransactionManager.class);
transactionStatus = txManager.getTransaction(
new DefaultTransactionDefinition()
);
}
@Override
public void afterEach(ExtensionContext context) throws Exception {
// Откатываем транзакцию
ApplicationContext appContext = SpringExtension.getApplicationContext(context);
PlatformTransactionManager txManager =
appContext.getBean(PlatformTransactionManager.class);
txManager.rollback(transactionStatus);
}
}
// Использование:
@SpringBootTest
@Transactional // автоматически регистрирует расширение
class UserServiceTest {
@Autowired
private UserService userService;
@Autowired
private UserRepository userRepository;
@Test
void testCreateUser() {
// Выполняется внутри транзакции, которая откатится после теста
User user = userService.createUser("John");
// В БД нет этого пользователя (транзакция откатана)
}
}
Лучшие практики
1. Используйте встроенные расширения Spring
// ПРАВИЛЬНО: используй @SpringBootTest
@SpringBootTest
class MyTest { }
// НЕПРАВИЛЬНО: не регистрируй SpringExtension вручную
// @ExtendWith(SpringExtension.class) // это делает @SpringBootTest
2. Держите Extensions простыми
// ХОРОШО: одна ответственность
public class DatabaseCleanupExtension implements BeforeEachCallback {
public void beforeEach(ExtensionContext context) {
// только чистка БД
}
}
// ПЛОХО: слишком много ответственности
public class MegaExtension implements BeforeEachCallback, AfterEachCallback,
ParameterResolver, TestInstancePreProcessor {
// Делаем всё сразу - трудно поддерживать
}
3. Комбинируйте Extensions правильно
@SpringBootTest
@ExtendWith({DatabaseCleanupExtension.class, LoggingExtension.class})
class UserServiceTest {
// Extensions выполняются по порядку
}
Заключение
Extensions в Spring Boot / JUnit 5:
- Современная архитектура для расширения фреймворка
- Заменили Rules из JUnit 4 более гибким механизмом
- Встроены в Spring Boot для удобства тестирования
- Мощный инструмент для реализации cross-cutting concerns в тестах (логирование, очистка БД, подготовка данных)