Отключить @EnableScheduling в весенних тестах
Когда я запускаю свои модульные тесты, он вызывает мои запланированные задачи. Я хочу предотвратить это поведение, которое вызвано тем, что у меня есть @EnableScheduling
на моей основной конфигурации приложения.
Как я могу отключить это на моих модульных тестах?
Я сталкивался с этим вопросом / ответом, который предлагает настроить профили?
Не уверен, как бы я поступил об этом? или если это перебор? Я думал о создании отдельной AppConfiguration для моих модульных тестов, но мне кажется, что я повторяю код дважды, когда я делаю это?
@Configuration
@EnableJpaRepositories(AppConfiguration.DAO_PACKAGE)
@EnableTransactionManagement
@EnableScheduling
@ComponentScan({AppConfiguration.SERVICE_PACKAGE,
AppConfiguration.DAO_PACKAGE,
AppConfiguration.CLIENT_PACKAGE,
AppConfiguration.SCHEDULE_PACKAGE})
public class AppConfiguration {
static final String MAIN_PACKAGE = "com.etc.app-name";
static final String DAO_PACKAGE = "com.etc.app-name.dao";
private static final String ENTITIES_PACKAGE = "com.etc.app-name.entity";
static final String SERVICE_PACKAGE = "com.etc.app-name.service";
static final String CLIENT_PACKAGE = "com.etc.app-name.client";
static final String SCHEDULE_PACKAGE = "com.etc.app-name.scheduling";
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(){
// stripped code for question readability
}
// more app config code below etc
}
Пример юнит-теста.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={AppConfiguration.class})
@Transactional
@TransactionConfiguration(defaultRollback = true)
@WebAppConfiguration
public class ExampleDaoTest {
@Autowired
ExampleDao exampleDao;
@Test
public void testExampleDao() {
List<Example> items = exampleDao.findAll();
Assert.assertTrue(items.size()>0);
}
}
13 ответов
Если вы не хотите использовать профили, вы можете добавить флаг, который будет включать / отключать планирование для приложения
В вашем AppConfiguration
Добавь это
@ConditionalOnProperty(
value = "app.scheduling.enable", havingValue = "true", matchIfMissing = true
)
@Configuration
@EnableScheduling
public static class SchedulingConfiguration {
}
и в своем тесте просто добавьте эту аннотацию, чтобы отключить планирование
@TestPropertySource(properties = "app.scheduling.enable=false")
Я просто параметризовал мою аннотацию @Scheduled с настраиваемым временем задержки:
@Scheduled(fixedRateString = "${timing.updateData}", initialDelayString = "${timing.initialDelay}")
В моем тестовом приложении. Yaml:
timing:
updateData: 60000
initialDelay: 10000000000
И главное приложение.yaml:
timing:
updateData: 60000
initialDelay: 1
Это не отключение, а создание такой длительной задержки, тесты пройдут задолго до того, как она запустится. Не самое элегантное решение, но одно из самых простых, которые я нашел.
Еще одно решение без каких-либо изменений в производственном коде с использованием @MockBean
.
@RunWith(SpringRunner.class)
@SpringBootTest
@MockBean(MyScheduledClass.class)
public class MyTest {
Что в конечном итоге заменит активное запланированное задание или создаст имитацию.
Из документации
Моки могут быть зарегистрированы по типу или по имени компонента {@link #name()}. Любой существующий одиночный компонент того же типа, который определен в контексте, будет заменен имитацией, если существующий компонент не определен, будет добавлен новый.
Альтернативой может быть отмена регистрации постпроцессора бина, который планирует события. Это можно сделать, просто поместив следующий класс в classpath ваших тестов:
public class UnregisterScheduledProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(final ConfigurableListableBeanFactory beanFactory) throws BeansException {
for (String beanName : beanFactory.getBeanNamesForType(ScheduledAnnotationBeanPostProcessor.class)) {
((DefaultListableBeanFactory)beanFactory).removeBeanDefinition(beanName);
}
}
}
Хотя это довольно просто и, похоже, делает эту работу, остерегайтесь того, что я не слишком много тестировал или проверял возможные последствия удаления определенного bean-компонента из реестра или уверенности в том, что упорядочение PostProcessors не будет проблемой...
С помощью Spring Boot и cron вы можете включить или отключить планирование. Например, вы можете определить тестовый application.yml и установить
scheduler:
cron-expr: '-'
См. Также отключение расписания с помощью '-'. В вашем классе планировщика вы можете передать выражение.
@Scheduled(cron = "${scheduler.cron-expr}")
Обнаружил, что добавление
app.scheduling.enable=false
в тестовом application.properties вместе с
@ConditionalOnProperty(value = "app.scheduling.enable", havingValue = "true", matchIfMissing = true)
@EnableScheduling
для планирования аннотаций классов конфигурации, как в ответе Марко Враньковича, работает для всех тестов без необходимости аннотировать каждый из них!
В каждом тесте вы определяете, какая конфигурация пружины должна использоваться, в настоящее время у вас есть:
@ContextConfiguration(classes={AppConfiguration.class})
Обычной практикой является определение отдельной конфигурации пружины для вашего обычного применения и для ваших испытаний.
AppConfiguration.java
TestConfiguration.java
Тогда в вашем тесте вы просто ссылка TestConfiguration
вместо вашего текущего AppConfiguration
с помощью @ContextConfiguration(classes={TestConfiguration.class})
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={TestConfiguration.class})
@Transactional
@TransactionConfiguration(defaultRollback = true)
@WebAppConfiguration
public class ExampleDaoTest
Таким образом, вы можете настроить любой параметр для ваших тестов иначе, чем в рабочем коде. Например, вы можете использовать базу данных в памяти для своих тестов вместо обычной и многое другое.
Я смог решить эту проблему, создав метод, который удаляет запланированные задачи во время модульных тестов. Вот пример:
import org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor;
import org.springframework.context.ApplicationContext;
public static void removeSchedulledTasks(ScheduledAnnotationBeanPostProcessor postProcessor, ApplicationContext appContext) {
postProcessor.setApplicationContext(appContext);
Iterator<ScheduledTask> iterator = postProcessor.getScheduledTasks().iterator();
while(iterator.hasNext()) {
ScheduledTask taskAtual = iterator.next();
taskAtual.cancel();
}
}
Пример использования:
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.example.Utils;
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestRemoveScheduller {
@Autowired
private ScheduledAnnotationBeanPostProcessor postProcessor;
@Autowired
private ApplicationContext appContext;
@Before
public void init(){
//Some init variables
//Remove schedulled tasks method
Utils.removeSchedulledTasks(postProcessor, appContext);
}
//Some test methods
}
Надеюсь это поможет.
Я решил это для весенних загрузочных приложений, отключив конфигурацию @EnableScheduling для тестового профиля:
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.scheduling.annotation.EnableScheduling;
@Configuration
@Profile({"!test"})
@EnableScheduling
public class SchedulingConfiguration {
}
Я хотел сделать это в обычном классе (а не в модульном тесте). У меня есть основное приложение Spring Boot, но мне нужен небольшой служебный класс для массовой очистки данных. Я хотел использовать весь контекст моего основного приложения, но отключить все запланированные задачи. Лучшее решение для меня было похоже на Gladson Bruno:
scheduledAnnotationBeanPostProcessor.getScheduledTasks().forEach(ScheduledTask::cancel);
Еще одним преимуществом этого подхода является то, что вы можете получить список всех запланированных задач и добавить логику для отмены некоторых задач, но не других.
Создайте
TestTaskScheduler
Бин в тестовом классе
public class TestTaskScheduler implements TaskScheduler {
private static final NullScheduledFuture NULL_SCHEDULED_FUTURE = new NullScheduledFuture();
@Override
public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {
return NULL_SCHEDULED_FUTURE;
}
@Override
public ScheduledFuture<?> schedule(Runnable task, Date startTime) {
return NULL_SCHEDULED_FUTURE;
}
@Override
public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Date startTime, long period) {
return NULL_SCHEDULED_FUTURE;
}
@Override
public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, long period) {
return NULL_SCHEDULED_FUTURE;
}
@Override
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Date startTime, long delay) {
return NULL_SCHEDULED_FUTURE;
}
@Override
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, long delay) {
return NULL_SCHEDULED_FUTURE;
}
private static class NullScheduledFuture implements ScheduledFuture {
@Override
public long getDelay(TimeUnit unit) {
return 0;
}
@Override
public int compareTo(Delayed o) {
return 0;
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return false;
}
@Override
public boolean isCancelled() {
return false;
}
@Override
public boolean isDone() {
return false;
}
@Override
public Object get() throws InterruptedException, ExecutionException {
return null;
}
@Override
public Object get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
return null;
}
}
}
Я бы выбрал TestExecutionListener, например:
public class SchedulerExecutionListener implements TestExecutionListener {
@Override
public void beforeTestClass(@NonNull TestContext testContext) {
try {
ScheduledAnnotationBeanPostProcessor schedulerProcessor = testContext.getApplicationContext().getBean(ScheduledAnnotationBeanPostProcessor.class);
schedulerProcessor.destroy();
} catch (Exception ignored) {
ignored.printStackTrace();
System.out.println(ignored.getMessage());
}
}
И затем вы добавляете его в свой testClass
@TestExecutionListeners(listeners = SchedulerExecutionListener .class, mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS)
class Test {}
Свойства моего приложения хранятся так просто: добавьте ConditionalOnProperty в планировщик:
@ConditionalOnProperty(value = "scheduling.enabled", havingValue = "true", matchIfMissing = true)
@EnableScheduling
И отключите среду разработки вapplication.yml
:
environments:
development:
scheduling:
enabled: false