Comment puis-je désactiver le démarrage automatique de la planification sur Spring Boot IntegrationTest?
Merci.
Sachez que des composants externes peuvent activer automatiquement la planification (voir HystrixStreamAutoConfiguration et MetricExportAutoConfiguration à partir de Spring Framework). Donc, si vous essayez d'utiliser @ConditionalOnProperty
ou @Profile
dans la classe @Configuration
qui spécifie @EnableScheduling
, la planification sera quand même activée en raison de composants externes.
Ayez une classe @Configuration
qui permet la planification via @EnableScheduling
, mais placez ensuite vos travaux planifiés dans des classes distinctes, chacune utilisant @ConditionalOnProperty
pour activer/désactiver les classes contenant les tâches @Scheduled.
N'aurez pas le @Scheduled
et le @EnableScheduling
dans la même classe, sinon vous aurez le problème lorsque les composants externes l'activeront quand même. Le @ConditionalOnProperty
est donc ignoré.
Par exemple:
@Configuration
@EnableScheduling
public class MyApplicationSchedulingConfiguration {
}
puis dans une classe séparée
@Named
@ConditionalOnProperty(value = "scheduling.enabled", havingValue = "true", matchIfMissing = false)
public class MyApplicationScheduledTasks {
@Scheduled(fixedRate = 60 * 60 * 1000)
public void runSomeTaskHourly() {
doStuff();
}
}
Le problème avec cette solution est que chaque travail planifié doit être dans sa propre classe avec @ConditionalOnProperty
spécifié. Si vous manquez cette annotation, le travail sera exécuté.
Étendez la ThreadPoolTaskScheduler
et substituez les méthodes TaskScheduler
. Dans ces méthodes, vous pouvez vérifier si le travail doit être exécuté.
Ensuite, dans votre classe @Configuration où vous utilisez @EnableScheduling, vous créez également un @Bean appelé taskScheduler qui retourne votre planificateur de tâches de pool de threads personnalisé).
Par exemple:
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;
}
}
Classe de configuration qui crée le bean taskScheduler à l'aide de notre planificateur personnalisé et active la planification
@Configuration
@EnableScheduling
public class MyApplicationSchedulingConfiguration {
@Bean
public TaskScheduler taskScheduler() {
return new ConditionalThreadPoolTaskScheduler();
}
}
Le problème potentiel avec ce qui précède est que vous avez créé une dépendance sur une classe Spring interne. Par conséquent, si des modifications devaient être apportées à l'avenir, vous devrez corriger la compatibilité.
J'ai eu le même problème. J'ai essayé l'attribut @ConditionalOnProperty
de Spring avec mon bean de planification, mais la planification était toujours activée dans les tests.
La seule solution de contournement que j'ai trouvée consiste à écraser les propriétés de planification de la classe Test afin que le travail ne puisse pas s'exécuter real.
Si votre travail réel s'exécute toutes les 5 minutes à l'aide de la propriété my.cron=0 0/5 * * * *
public class MyJob {
@Scheduled(cron = "${my.cron}")
public void execute() {
// do something
}
}
Ensuite, dans la classe de test, vous pouvez le configurer comme suit:
@RunWith(SpringRunner.class)
@SpringBootTest(properties = {"my.cron=0 0 0 29 2 ?"}) // Configured as 29 Feb ;-)
public class MyApplicationTests {
@Test
public void contextLoads() {
}
}
Ainsi, même si votre travail est activé, il ne sera exécuté qu'à la 0ème heure du 29 février, soit une fois tous les 4 ans. Donc, vous avez une chance très mince de l'exécuter.
Vous pouvez proposer des paramètres cron plus sophistiqués pour répondre à vos besoins.
Une façon consiste à utiliser des profils Spring
Dans votre classe de test:
@SpringBootTest(classes = Application.class)
@ActiveProfiles("integration-test")
public class SpringBootTestBase {
...
}
Dans votre classe ou méthode de planificateur:
@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
}
...
}
Lorsque votre vraie classe d'application de démarrage Spring ressemble à ceci:
@SpringBootApplication
@EnableScheduling
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
vous devrez créer une autre classe d'application sans @EnableScheduling pour vos tests d'intégration, comme ceci:
@SpringBootApplication
public class MyTestApplication {
public static void main(String[] args) {
SpringApplication.run(MyTestApplication.class, args);
}
}
Et utilisez ensuite la classe MyTestApplication dans votre test d'intégration comme ceci
RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = MyTestApplication.class)
public class MyIntegrationTest {
...
}
C'est comme ça que je le fais, car je n'ai pas trouvé de meilleur moyen.
J'ai résolu ce problème en utilisant une classe de configuration séparée puis en écrasant cette classe dans le contexte du test. Ainsi, au lieu de mettre l'annotation à l'application, je ne la mets qu'aux classes de configuration séparées.
Contexte normal:
@Configuration
@EnableScheduling
public class SpringConfiguration {}
Contexte de test:
@Configuration
public class SpringConfiguration {}
Une solution facile que j'ai trouvée dans Spring Boot 2.0.3:
1) extraire les méthodes programmées dans un haricot séparé
@Service
public class SchedulerService {
@Autowired
private SomeTaskService someTaskService;
@Scheduled(fixedRate = 60 * 60 * 1000)
public void runSomeTaskHourly() {
someTaskService.runTask();
}
}
2) simulez le bean programmateur dans votre classe de test
@RunWith(SpringRunner.class)
@SpringBootTest
public class SomeTaskServiceIT {
@Autowired
private SomeTaskService someTaskService;
@MockBean
private SchedulerService schedulerService;
}