web-dev-qa-db-fra.com

Désactiver @Schedule on Spring Boot IntegrationTest

Comment puis-je désactiver le démarrage automatique de la planification sur Spring Boot IntegrationTest?

Merci.

14
assoline

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.

Une solution

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é.

Une autre solution

É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é.

15
Duke0fAnkh

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.

2
MandeSin

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
    }

    ...
}
1
Samuel Morais

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.

1
Tom

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 {}
0
Tobske

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;
}
0
Michal B