Можно ли указать любой класс в @ExtendWith?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Можно ли указать любой класс в @ExtendWith?
Краткий ответ
Нет, нельзя. В @ExtendWith можно использовать только классы, которые реализуют интерфейс Extension из JUnit 5. Попытка использовать произвольный класс приведёт к ошибке при запуске тестов.
Что такое @ExtendWith
@ExtendWith - это аннотация в JUnit 5 (Jupiter), которая регистрирует Extension'ы для модификации поведения тестов. Extension - это механизм для внедрения кастомной логики в жизненный цикл тестов.
Требования к классу для @ExtendWith
Класс ДОЛЖЕН реализовать хотя бы один из интерфейсов Extension'а:
// Основной интерфейс (маркер)
public interface Extension {}
// Расширения для различных стадий жизненного цикла теста
public interface BeforeAllCallback extends Extension {}
public interface AfterAllCallback extends Extension {}
public interface BeforeEachCallback extends Extension {}
public interface AfterEachCallback extends Extension {}
public interface BeforeTestExecutionCallback extends Extension {}
public interface AfterTestExecutionCallback extends Extension {}
public interface ParameterResolver extends Extension {}
public interface TestInstanceFactory extends Extension {}
public interface TestInstancePostProcessor extends Extension {}
Примеры правильного использования
Правильно: Extension реализует интерфейс
// Кастомный Extension
public class DatabaseExtension implements BeforeEachCallback, AfterEachCallback {
@Override
public void beforeEach(ExtensionContext context) throws Exception {
System.out.println("Подготовка БД");
// Создаём тестовую БД или очищаем данные
}
@Override
public void afterEach(ExtensionContext context) throws Exception {
System.out.println("Очистка БД");
// Удаляем тестовые данные
}
}
// Использование в тесте
@ExtendWith(DatabaseExtension.class)
class UserRepositoryTest {
@Test
void shouldCreateUser() {
// БД подготовлена благодаря Extension
User user = new User("John");
// ...
}
}
НЕПРАВИЛЬНО: Произвольный класс
// Обычный класс, не реализующий Extension
public class MyUtilityClass {
public static void doSomething() {}
}
// ❌ ОШИБКА! ClassCastException при запуске
@ExtendWith(MyUtilityClass.class) // Компилируется, но упадёт в runtime
class MyTest {
@Test
void test() {}
}
Ошибка:
java.lang.ClassCastException: class MyUtilityClass
cannotbe cast to class org.junit.jupiter.api.extension.Extension
Практические примеры Extension'ов
1. Extension для очистки ресурсов
public class ResourceCleanupExtension implements AfterEachCallback {
private List<Closeable> resources = new ArrayList<>();
public void registerResource(Closeable resource) {
resources.add(resource);
}
@Override
public void afterEach(ExtensionContext context) throws Exception {
for (Closeable resource : resources) {
try {
resource.close();
} catch (IOException e) {
System.err.println("Failed to close resource: " + e.getMessage());
}
}
}
}
2. Extension для инъекции параметров в тесты
public class RandomNumberExtension implements ParameterResolver {
@Override
public boolean supportsParameter(ParameterContext parameterContext,
ExtensionContext extensionContext) {
return parameterContext.getParameter().getType() == RandomData.class;
}
@Override
public Object resolveParameter(ParameterContext parameterContext,
ExtensionContext extensionContext) {
return new RandomData();
}
}
// Использование
@ExtendWith(RandomNumberExtension.class)
class RandomTest {
@Test
void testWithRandomData(RandomData data) { // Автоматически инъектится
assertNotNull(data);
}
}
3. Extension для логирования
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();
String status = context.getExecutionException().isPresent() ? "❌ FAILED" : "✅ PASSED";
logger.info("{} Конец теста: {}", status, testName);
}
}
4. Встроенные JUnit 5 Extension'ы
JUnit 5 поставляется с готовыми Extension'ами:
// MockitoExtension для работы с Mockito
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
void testGetUser() {
// Mocks автоматически созданы и инъектены
when(userRepository.findById(1L)).thenReturn(new User(1L, "John"));
User user = userService.getUser(1L);
assertEquals("John", user.getName());
}
}
Композиция Extension'ов
Можно комбинировать несколько Extension'ов:
@ExtendWith(DatabaseExtension.class)
@ExtendWith(LoggingExtension.class)
@ExtendWith(MockitoExtension.class)
class ComplexTest {
@Test
void complexTest() {
// БД подготовлена
// Логирование включено
// Mocks инъектены
}
}
// Или в одной аннотации:
@ExtendWith({DatabaseExtension.class, LoggingExtension.class, MockitoExtension.class})
class ComplexTest {
// ...
}
Проверка типа Extension'а
import org.junit.jupiter.api.extension.Extension;
public class ExtensionValidator {
public static void validate(Class<?> clazz) {
if (!Extension.class.isAssignableFrom(clazz)) {
throw new IllegalArgumentException(
clazz.getName() + " не реализует интерфейс Extension"
);
}
}
}
Типичные ошибки
Ошибка 1: Забыли реализовать Extension
// ❌ ПЛОХО
public class MyExtension { // Не реализует Extension
public void beforeEach() {}
}
// ✅ ХОРОШО
public class MyExtension implements BeforeEachCallback {
@Override
public void beforeEach(ExtensionContext context) {}
}
Ошибка 2: Забыли переопределить методы
// ❌ ПЛОХО
public class MyExtension implements BeforeEachCallback {
// Метод не переопределён!
}
// ✅ ХОРОШО
public class MyExtension implements BeforeEachCallback {
@Override
public void beforeEach(ExtensionContext context) throws Exception {
// Реализация
}
}
Ошибка 3: Неправильно использовал встроенный Extension
// ❌ ПЛОХО
@ExtendWith(UserRepository.class) // UserRepository - это сервис, не Extension
class TestClass {
// ...
}
// ✅ ХОРОШО
@ExtendWith(MockitoExtension.class) // MockitoExtension реализует Extension
class TestClass {
@Mock
private UserRepository userRepository;
}
Лучшие практики
- Всегда реализуй хотя бы один интерфейс Extension
- Используй встроенные Extension'ы если они подходят (MockitoExtension, SpringExtension)
- Делай Extension'ы простыми и сосредоточенными
- Документируй поведение Extension'а в классе
- Избегай состояния в Extension'ах, используй ExtensionContext
Вывод
@ExtendWith требует, чтобы класс реализовал интерфейс Extension. Это гарантирует, что класс знает, как взаимодействовать с JUnit 5 жизненным циклом. Попытка использовать произвольный класс приведёт к runtime ошибке ClassCastException.