← Назад к вопросам

Можно ли указать любой класс в @ExtendWith?

2.0 Middle🔥 131 комментариев
#Тестирование

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Можно ли указать любой класс в @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.

Можно ли указать любой класс в @ExtendWith? | PrepBro