Disable @Schedule on Spring Boot IntegrationTest
Asked Answered
W

7

19

How can I disable the schedule auto-start on Spring Boot IntegrationTest?

Thanks.

Willywillynilly answered 18/11, 2016 at 19:45 Comment(4)
Possible duplicate of How to conditionally enable or disable scheduled jobs in Spring?Emotive
Possible duplicate of Disable @EnableScheduling on Spring TestsSlumberland
I can't believe there's no good answer to this question. Very surprising.Christinchristina
The only solution I got to work was to use the @Profile - annotation, by creating a test profile and a production profile. ProTip: Do not forget to activate the production profile in your src/main/resources/application.properties if you want to avoid your colleagues hating you. ;)Slumberland
D
22

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.

One solution

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.

Another Solution

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.

Ditter answered 17/10, 2017 at 5:57 Comment(3)
I have made mine work with the 1st solution. Thanks!Stirring
I just have my own flag in doStuff I make the check, if its not enabled then do nothing, let schedulars run, they end up doing nothingLuminosity
Thanks! I was trying to use @ConditionalOnProperty on the Configuration class, but does not work this way.Richey
I
10

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.

International answered 24/7, 2017 at 15:2 Comment(1)
You could also set the cron value to - via properties, as this is reserved expression meaning never runLovering
R
5

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;
}
Rely answered 27/7, 2018 at 10:12 Comment(1)
disable @Schedule service on Spring Boot Test, by mocking its beanLindalindahl
S
2

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
    }

    ...
}
Stillage answered 10/9, 2018 at 15:0 Comment(1)
pretty bad if you have many tests written by other people.Facient
W
1

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.

Woad answered 18/11, 2016 at 22:26 Comment(2)
This will only work in very special setups. Since @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
Yes this looked so promising then as @Slumberland suggest @EnableScheduling still kicked itAphasic
C
1

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 {}
Cowry answered 18/6, 2018 at 11:14 Comment(0)
A
0

Integrating some answers from above:

  • Create a separate configuration class for testing purposes (a.k.a "TestConfiguration.class")
  • 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:

  • "application.yml" self-creation properties depending on external devices/services
  • Auto-configuration annotations on Main class breaking beans initialization sequence
  • "applicationContext.xml", "beans.xml" or Classpath loaders

Please, read these:

Adder answered 7/6, 2019 at 19:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.