How can I disable the schedule auto-start on Spring Boot IntegrationTest?
Thanks.
How can I disable the schedule auto-start on Spring Boot IntegrationTest?
Thanks.
Be aware that external components could be enabling scheduling automatically (see HystrixStreamAutoConfiguration and MetricExportAutoConfiguration from the Spring Framework). So if you try and use @ConditionalOnProperty
or @Profile
on the @Configuration
class that specifies @EnableScheduling
, then scheduling will be enabled anyway due to external components.
Have one @Configuration
class that enables scheduling via @EnableScheduling
, but then have your scheduled jobs in separate classes, each of those using @ConditionalOnProperty
to enable/disable the classes that contain the @Scheduled tasks.
Don't have the @Scheduled
and @EnableScheduling
in the same class, or you will have the issue where external components are enabling it anyway, so the @ConditionalOnProperty
is ignored.
Eg:
@Configuration
@EnableScheduling
public class MyApplicationSchedulingConfiguration {
}
and then in a separate class
@Named
@ConditionalOnProperty(value = "scheduling.enabled", havingValue = "true", matchIfMissing = false)
public class MyApplicationScheduledTasks {
@Scheduled(fixedRate = 60 * 60 * 1000)
public void runSomeTaskHourly() {
doStuff();
}
}
The issue with this solution is that every scheduled job needs to be in it's own class with @ConditionalOnProperty
specified. If you miss that annotation, then the job will run.
Extend the ThreadPoolTaskScheduler
and override the TaskScheduler
methods. In these methods you can perform a check to see if the job should run.
Then, in your @Configuration class where you use @EnableScheduling, you also create a @Bean called taskScheduler which returns your custom thread pool task scheduler).
Eg:
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;
}
}
Configuration class that creates the taskScheduler bean using our custom scheduler, and enables scheduling
@Configuration
@EnableScheduling
public class MyApplicationSchedulingConfiguration {
@Bean
public TaskScheduler taskScheduler() {
return new ConditionalThreadPoolTaskScheduler();
}
}
The potential issue with the above is that you've created a dependency on an internal Spring class, so if there are changes in the future, you'd have to fix compatibility.
@ConditionalOnProperty
on the Configuration class, but does not work this way. –
Richey I had the same problem. Tried Spring's @ConditionalOnProperty
attribute with my Scheduling Bean but Scheduling still got activated in tests.
The only good workaround I found was to overwrite the scheduling properties in the Test class, so that the job does not have a real chance to run.
If your real job runs every 5 minutes using property my.cron=0 0/5 * * * *
public class MyJob {
@Scheduled(cron = "${my.cron}")
public void execute() {
// do something
}
}
Then in the test class you can configure it as:
@RunWith(SpringRunner.class)
@SpringBootTest(properties = {"my.cron=0 0 0 29 2 ?"}) // Configured as 29 Feb ;-)
public class MyApplicationTests {
@Test
public void contextLoads() {
}
}
So even if your job is activated, it will only run at the 0th hour of 29 Feb which happens once in 4 years. So you have a very slim chance of running it.
You can come up with more fancy cron settings to suit your requirements.
-
via properties, as this is reserved expression meaning never run –
Lovering An easy solution I figured out in Spring Boot 2.0.3:
1) extract scheduled method(s) to a separate bean
@Service
public class SchedulerService {
@Autowired
private SomeTaskService someTaskService;
@Scheduled(fixedRate = 60 * 60 * 1000)
public void runSomeTaskHourly() {
someTaskService.runTask();
}
}
2) mock the scheduler bean in your test class
@RunWith(SpringRunner.class)
@SpringBootTest
public class SomeTaskServiceIT {
@Autowired
private SomeTaskService someTaskService;
@MockBean
private SchedulerService schedulerService;
}
One way is to use Spring profiles
In your test class:
@SpringBootTest(classes = Application.class)
@ActiveProfiles("integration-test")
public class SpringBootTestBase {
...
}
In your scheduler class or method:
@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
}
...
}
When your real Spring Boot Application class looks like this:
@SpringBootApplication
@EnableScheduling
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
you would have to create another Application class without @EnableScheduling for your integration tests like this:
@SpringBootApplication
public class MyTestApplication {
public static void main(String[] args) {
SpringApplication.run(MyTestApplication.class, args);
}
}
And then use the MyTestApplication class in your integration test like this
RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = MyTestApplication.class)
public class MyIntegrationTest {
...
}
That's the way I do it, since I have not found a better way.
@SpringBootApplication
includes the @ComponentScan
annotation, even if you specify MyTestApplication to be used by your tests, usually the @ComponentScan
part is going to find the other class annotated with @SpringBootApplication
apply its configuration as well and voila your @EnableScheduling
is back on. For this to work you need to have both classes in different packages on the same level, which makes the whole auto configuration useless. –
Slumberland @EnableScheduling
still kicked it –
Aphasic I solved this problem by using a separate configuration class and then overwrite this class in the test context. So instead of putting the annotation at the application I only put it at the separate configuration classes.
Normal context:
@Configuration
@EnableScheduling
public class SpringConfiguration {}
Test context:
@Configuration
public class SpringConfiguration {}
Integrating some answers from above:
Enable Mockito annotations there for other beans: schedulers, etc. - Read this: 2)
@ConditionalOnClass
@ConditionalOnMissingBean
@ConditionalOnBean
@ConditionalOnJava
@ConditionalOnJndi
@ConditionalOnMissingClass
@ConditionalOnExpression
@ConditionalOnNotWebApplication
@ConditionalOnWebApplication
@ConditionalOnProperty
@ConditionalOnResource
@ConditionalOnSingleCandidate
Plus, always checking:
Please, read these:
Try using mocking annotation on non-required beans for testing such as:
@MockBean
private StoresRatingAvgScheduler scheduler;
@Before
public void init() {
MockitoAnnotations.initMocks(this);
}
© 2022 - 2024 — McMap. All rights reserved.