Как отключить автозапуск расписания на Spring Boot IntegrationTest?
Спасибо.
Как отключить автозапуск расписания на Spring Boot IntegrationTest?
Спасибо.
Помните, что внешние компоненты могут автоматически включать планирование (см. HystrixStreamAutoConfiguration и MetricExportAutoConfiguration из Spring Framework). Поэтому, если вы попытаетесь использовать @ConditionalOnProperty
или @Profile
в классе @Configuration
, который указывает @EnableScheduling
, тогда планирование будет в любом случае включено из-за внешних компонентов.
У вас есть один класс @Configuration
, который позволяет планировать через @EnableScheduling
, но затем ваши запланированные задания в отдельных классах, каждый из которых использует @ConditionalOnProperty
для включения/выключения классов, содержащих задачи @Scheduled.
У вас нет @Scheduled
и @EnableScheduling
в том же классе, иначе у вас будет проблема, когда внешние компоненты все равно разрешают его, поэтому @ConditionalOnProperty
игнорируется.
Например:
@Configuration
@EnableScheduling
public class MyApplicationSchedulingConfiguration {
}
а затем в отдельном классе
@Named
@ConditionalOnProperty(value = "scheduling.enabled", havingValue = "true", matchIfMissing = false)
public class MyApplicationScheduledTasks {
@Scheduled(fixedRate = 60 * 60 * 1000)
public void runSomeTaskHourly() {
doStuff();
}
}
Проблема с этим решением заключается в том, что каждое запланированное задание должно быть в нем собственного класса с указанным @ConditionalOnProperty
. Если вы пропустите эту аннотацию, работа запустится.
Расширьте ThreadPoolTaskScheduler
и переопределите методы TaskScheduler
. В этих методах вы можете выполнить проверку, чтобы проверить, должно ли выполняться задание.
Затем в вашем классе @Configuration, где вы используете @EnableScheduling, вы также создаете @ Bean вызванный taskScheduler, который возвращает ваш планировщик заданий для пула потоков.)
Например:
public class ConditionalThreadPoolTaskScheduler extends ThreadPoolTaskScheduler {
@Inject
private Environment environment;
// Override the TaskScheduler methods
@Override
public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {
if (!canRun()) {
return null;
}
return super.schedule(task, trigger);
}
@Override
public ScheduledFuture<?> schedule(Runnable task, Date startTime) {
if (!canRun()) {
return null;
}
return super.schedule(task, startTime);
}
@Override
public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Date startTime, long period) {
if (!canRun()) {
return null;
}
return super.scheduleAtFixedRate(task, startTime, period);
}
@Override
public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, long period) {
if (!canRun()) {
return null;
}
return super.scheduleAtFixedRate(task, period);
}
@Override
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Date startTime, long delay) {
if (!canRun()) {
return null;
}
return super.scheduleWithFixedDelay(task, startTime, delay);
}
@Override
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, long delay) {
if (!canRun()) {
return null;
}
return super.scheduleWithFixedDelay(task, delay);
}
private boolean canRun() {
if (environment == null) {
return false;
}
if (!Boolean.valueOf(environment.getProperty("scheduling.enabled"))) {
return false;
}
return true;
}
}
Класс конфигурации, который создает taskScheduler bean с помощью нашего настраиваемого планировщика и позволяет планировать
@Configuration
@EnableScheduling
public class MyApplicationSchedulingConfiguration {
@Bean
public TaskScheduler taskScheduler() {
return new ConditionalThreadPoolTaskScheduler();
}
}
Потенциальная проблема с вышеизложенным заключается в том, что вы создали зависимость от внутреннего класса Spring, поэтому, если в будущем будут изменения, вам придется исправлять совместимость.
У меня была та же проблема. Попробовал Spring @ConditionalOnProperty
атрибут с моим расписанием Bean, но планирование все еще активировалось в тестах.
Единственное удобное обходное решение, которое я нашел, - это перезаписать свойства планирования в классе Test, так что задание не имеет шанса real.
Если ваша реальная работа выполняется каждые 5 минут, используя свойство my.cron=0 0/5 * * * *
public class MyJob {
@Scheduled(cron = "${my.cron}")
public void execute() {
// do something
}
}
Затем в тестовом классе вы можете настроить его как:
@RunWith(SpringRunner.class)
@SpringBootTest(properties = {"my.cron=0 0 0 29 2 ?"}) // Configured as 29 Feb ;-)
public class MyApplicationTests {
@Test
public void contextLoads() {
}
}
Таким образом, даже если ваша работа активирована, она будет работать только в 0-й час 29 февраля, который происходит один раз в 4 года. Таким образом, у вас очень тонкий шанс запустить его.
Вы можете придумать более удобные настройки cron в соответствии с вашими требованиями.
Когда ваш реальный класс загрузки Spring выглядит следующим образом:
@SpringBootApplication
@EnableScheduling
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
вам понадобится создать еще один класс приложения без @EnableScheduling для ваших интеграционных тестов, например:
@SpringBootApplication
public class MyTestApplication {
public static void main(String[] args) {
SpringApplication.run(MyTestApplication.class, args);
}
}
И затем используйте класс MyTestApplication в вашем тесте интеграции, подобном этому
RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = MyTestApplication.class)
public class MyIntegrationTest {
...
}
То, как я это делаю, поскольку я не нашел лучшего способа.
Простое решение, которое я понял в Spring Boot 2.0.3:
1) извлечь запланированный метод в отдельный компонент
@Service
public class SchedulerService {
@Autowired
private SomeTaskService someTaskService;
@Scheduled(fixedRate = 60 * 60 * 1000)
public void runSomeTaskHourly() {
someTaskService.runTask();
}
}
2) издевайтесь над планировщиком в вашем тестовом классе
@RunWith(SpringRunner.class)
@SpringBootTest
public class SomeTaskServiceIT {
@Autowired
private SomeTaskService someTaskService;
@MockBean
private SchedulerService schedulerService;
}
Одним из способов является использование профилей Spring
В вашем тестовом классе:
@SpringBootTest(classes = Application.class)
@ActiveProfiles("integration-test")
public class SpringBootTestBase {
...
}
В вашем классе или методе планировщика:
@Configuration
@Profile("!integration-test") //to disable all from this configuration
public class SchedulerConfiguration {
@Scheduled(cron = "${some.cron}")
@Profile("!integration-test") //to disable a specific scheduler
public void scheduler1() {
// do something
}
@Scheduled(cron = "${some.cron}")
public void scheduler2() {
// do something
}
...
}
Я решил эту проблему, используя отдельный класс конфигурации, а затем переписал этот класс в контексте теста. Поэтому вместо размещения аннотации в приложении я размещаю ее только в отдельных классах конфигурации.
Нормальный контекст:
@Configuration
@EnableScheduling
public class SpringConfiguration {}
Тестовый контекст:
@Configuration
public class SpringConfiguration {}
Интегрируя некоторые ответы сверху:
Включите аннотации Mockito для других компонентов: планировщиков и т.д. - Read this: 2)
@ConditionalOnClass
@ConditionalOnMissingBean
@ConditionalOnBean
@ConditionalOnJava
@ConditionalOnJndi
@ConditionalOnMissingClass
@ConditionalOnExpression
@ConditionalOnNotWebApplication
@ConditionalOnWebApplication
@ConditionalOnProperty
@ConditionalOnResource
@ConditionalOnSingleCandidate
Плюс всегда проверяю:
Пожалуйста, прочитайте это:
Попробуйте использовать фиктивную аннотацию на ненужных bean-компонентах для тестирования, такого как:
@MockBean
private StoresRatingAvgScheduler scheduler;
@Before
public void init() {
MockitoAnnotations.initMocks(this);
}