Подтвердить что ты не робот

Отключить @Schedule на Spring Boot IntegrationTest

Как отключить автозапуск расписания на Spring Boot IntegrationTest?

Спасибо.

4b9b3361

Ответ 1

Помните, что внешние компоненты могут автоматически включать планирование (см. 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, поэтому, если в будущем будут изменения, вам придется исправлять совместимость.

Ответ 2

У меня была та же проблема. Попробовал 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 в соответствии с вашими требованиями.

Ответ 3

Когда ваш реальный класс загрузки 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 {

...
}

То, как я это делаю, поскольку я не нашел лучшего способа.

Ответ 4

Простое решение, которое я понял в 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;
}

Ответ 5

Одним из способов является использование профилей 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
    }

    ...
}

Ответ 6

Я решил эту проблему, используя отдельный класс конфигурации, а затем переписал этот класс в контексте теста. Поэтому вместо размещения аннотации в приложении я размещаю ее только в отдельных классах конфигурации.
Нормальный контекст:

@Configuration
@EnableScheduling 
public class SpringConfiguration {}

Тестовый контекст:

@Configuration
public class SpringConfiguration {}

Ответ 7

Интегрируя некоторые ответы сверху:

  • Создайте отдельный класс конфигурации для целей тестирования (он же "TestConfiguration.class")
  • Включите аннотации Mockito для других компонентов: планировщиков и т.д. - Read this: 2)

    @ConditionalOnClass
    @ConditionalOnMissingBean
    @ConditionalOnBean
    @ConditionalOnJava
    @ConditionalOnJndi
    @ConditionalOnMissingClass
    @ConditionalOnExpression
    @ConditionalOnNotWebApplication
    @ConditionalOnWebApplication
    @ConditionalOnProperty
    @ConditionalOnResource
    @ConditionalOnSingleCandidate
    

Плюс всегда проверяю:

  • Свойства "application.yml" для самостоятельного создания в зависимости от внешних устройств/сервисов
  • Автоконфигурация аннотаций на последовательности инициализации разрыва бинов Main class
  • "applicationContext.xml", "beans.xml" или загрузчики Classpath

Пожалуйста, прочитайте это: