Disable @EnableScheduling on Spring Tests
Asked Answered
N

13

100

When I run my unit tests, it invokes my scheduled tasks. I want to prevent this behaviour, which is caused by the fact that I have @EnableScheduling on my main app configuration.

How can I disable this on my unit tests?

I have come across this question/answer which suggests setting up profiles?

Not sure how I would go about that? or if its an overkill? I was thinking of having a separate AppConfiguration for my unit tests but it feels like im repeating code twice when I do that?

@Configuration
@EnableJpaRepositories(AppConfiguration.DAO_PACKAGE)
@EnableTransactionManagement
@EnableScheduling
@ComponentScan({AppConfiguration.SERVICE_PACKAGE,
                AppConfiguration.DAO_PACKAGE,
                AppConfiguration.CLIENT_PACKAGE,
                AppConfiguration.SCHEDULE_PACKAGE})
public class AppConfiguration {

    static final    String MAIN_PACKAGE             = "com.etc.app-name";
    static final    String DAO_PACKAGE              = "com.etc.app-name.dao";
    private static  final  String ENTITIES_PACKAGE  = "com.etc.app-name.entity";
    static final    String SERVICE_PACKAGE          = "com.etc.app-name.service";
    static final    String CLIENT_PACKAGE           = "com.etc.app-name.client";
    static final    String SCHEDULE_PACKAGE         = "com.etc.app-name.scheduling";


    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(){
       // stripped code for question readability
    }

    // more app config code below etc

}

Unit test example.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={AppConfiguration.class})
@Transactional
@TransactionConfiguration(defaultRollback = true)
@WebAppConfiguration
public class ExampleDaoTest {

    @Autowired
    ExampleDao exampleDao;

    @Test
    public void testExampleDao() {
        List<Example> items = exampleDao.findAll();
        Assert.assertTrue(items.size()>0);
    }
}
Nobe answered 12/3, 2015 at 15:57 Comment(4)
Why do you want to use spring context for unit testing?Malnutrition
so it autowires my objects so I can test my dao's and services?Nobe
You could just mock the Dao. Best would be use some framework like Mockito.Malnutrition
The correct answer is not in this thread, but found here: #40227437Mown
S
111

If you don't want to use profiles, you can add flag that will enable/disable scheduling for the application

In your AppConfiguration add this

  @ConditionalOnProperty(
     value = "app.scheduling.enable", havingValue = "true", matchIfMissing = true
  )
  @Configuration
  @EnableScheduling
  public static class SchedulingConfiguration {
  }

and in your test just add this annotation to disable scheduling

@TestPropertySource(properties = "app.scheduling.enable=false")
Stylus answered 4/3, 2017 at 15:1 Comment(6)
somehow it killed my spring boot configurationsBrenda
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. see https://mcmap.net/q/212629/-disable-schedule-on-spring-boot-integrationtestFloorboard
Sisyphus makes the same point I was going to make. I would add that MetricExportAutoConfiguration comes from Spring Boot Actuator and cane be disabled in a test profile with : spring.metrics.export.enabled: falseAstrahan
As addition, you can set "app.scheduling.enable=false" in your application properties for tests, so that you don't need to add TestPropertySource annotation to each and every test.Jessie
If it kills whole application, annotate particular beans containing @Scheduled with @ConditionalOnProperty(value = "app.scheduling.enable", ... ) instead of of bean having @EnableScheduling annotation.Implied
Alternatively, you can just move the SchedulingConfiguration out into its own class rather than be a static inner class, and drop the @ConditionalOnProperty. This will get picked up automatically if you use @SpringBootApplication but will get ignored by the tests. Note, you might have to tweak your scan paths to find the configurations. docs.spring.io/spring-boot/docs/2.4.13/reference/html/…Fairman
R
38

I just parameterized my @Scheduled annotation with configurable delay times:

@Scheduled(fixedRateString = "${timing.updateData}", initialDelayString = "${timing.initialDelay}")

In my test application.yaml:

timing:
    updateData: 60000
    initialDelay: 10000000000

And main application.yaml:

timing:
    updateData: 60000
    initialDelay: 1

It's not turning it off but creating such a long delay, the tests will be long over before it runs. Not the most elegant solution but definitely one of the easiest I've found.

Rhumb answered 29/7, 2018 at 19:14 Comment(2)
Not a clean solution. but it does its work. Have an up-vote just for this different angleEyeleteer
Out of the box thinking and also taught me a configuration I can use throughout my sever for setting a general fixed delay... Fantastic if you ask me :)Kasey
T
30

One more solution without any change in production code, using the @MockBean.

@RunWith(SpringRunner.class)
@SpringBootTest
@MockBean(MyScheduledClass.class)
public class MyTest {

Which will eventually replace active scheduled job or create a mocked one.

From the documentation

Mocks can be registered by type or by {@link #name() bean name}. Any existing single bean of the same type defined in the context will be replaced by the mock, if no existing bean is defined a new one will be added.

Trillium answered 12/5, 2020 at 13:9 Comment(4)
Very nice, best answer 👍Alexandros
But the question is how to disable the scheduling mechanism alone, as per your answer, the whole bean will be mocked, we cannot even use it as a normal spring bean. For me, @Laila Sharshar answer worked correctlyWhitley
This is by far the best and IMHO most clean answer. I always split out scheduling into a thin separate layer (i.e., an extra bean) to separate concerns, anyway, so @PrasanthRajendran comment doesn't apply to me.Osteitis
Keeping the scheduling in a separate bean is indeed a very good practice, otherwise single responsibility principle is not maintained properly. Thanks for explicitly mentioning that @StefanHaberlTrillium
T
22

An alternative would be to unregister the bean post processor that schedules the events. This can be done by simply putting the following class on the classpath of your tests:

@Component
public class UnregisterScheduledProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(final ConfigurableListableBeanFactory beanFactory) throws BeansException {
        for (String beanName : beanFactory.getBeanNamesForType(ScheduledAnnotationBeanPostProcessor.class)) {
            ((DefaultListableBeanFactory)beanFactory).removeBeanDefinition(beanName);
        }
    }
}

While this is quite simple and seems to do the job, beware that I did not test this very much or check for possible implications of removing a defined bean from the registry or making sure that ordering of PostProcessors won't be an issue...

Therese answered 10/1, 2017 at 10:58 Comment(5)
Just to give some feedback - I just tried this method and it appears to work just fine.Sizing
I have tried @vojtech-ruzicka and @lolotron answers plus other answers on this similar question here Likewise I must add that only @Therese answer worked for me. With a @Configuration annotation on his example UnregisterScheduledProcessor class for it to be auto loaded by Spring under my test. So far I have not experience any issues with it.Bechler
This was the best solution for me, as I preferred not to modify existing production code just for the sake of tests. I simply added this to my test classpath as a Spring @Component, and it's working so far, great solution!Aircraftman
To reference the BeanFactory for the above solution: #18488095Wailoo
This solved it for me. Thanks! Attention, as I need to annotate the class with @Component. That's all that was missing.Hinojosa
R
20

With Spring Boot and cron expression you can enable or disable scheduling. For example you can define an test application.yml and set

scheduler:
  cron-expr: '-'

See also disable scheduling with '-'. In your scheduler class you can pass the expression.

@Scheduled(cron = "${scheduler.cron-expr}")
Rorie answered 28/9, 2020 at 15:52 Comment(1)
This should be the new accepted answer; it's so much better than everything else listed hereBelting
W
5

Discovered that adding

app.scheduling.enable=false

in test application.properties along with

@ConditionalOnProperty(value = "app.scheduling.enable", havingValue = "true", matchIfMissing = true)
@EnableScheduling

to scheduling configuration class annotations like in Marko Vranjkovic's answer works for all tests without need to annotate each of them!

Wallywalnut answered 12/10, 2021 at 12:53 Comment(0)
F
2

In each Test you define which spring configuration should be used, currently you have:

@ContextConfiguration(classes={AppConfiguration.class})

Common practice is to define separate spring configuration for your normal application and for your tests.

AppConfiguration.java 
TestConfiguration.java

Then in your test you simply refference TestConfiguration instead of your current AppConfiguration using @ContextConfiguration(classes={TestConfiguration.class})

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={TestConfiguration.class})
@Transactional
@TransactionConfiguration(defaultRollback = true)
@WebAppConfiguration
public class ExampleDaoTest

This way you can configure any setting for your tests differently than in production code. You can for example use in-memory database for your tests instead of regular one and much more.

Floatation answered 12/3, 2015 at 16:20 Comment(3)
Unfortunately it does not work that way. The test uses the TestConfiguration class but it still also uses the annotations declared on the TestConfiguration. :(Gerber
You may set up a test configuration class or an active profiles to pick up from the properties file, however by doing this you don't avoid the annotated class being scheduled.Theiss
#40227437 - This one worked for meTheiss
N
2

I was able to solve this problem by creating a method that removes the scheduled tasks during unit tests. Here is an example:

    import org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor;
    import org.springframework.context.ApplicationContext;

    public static void removeScheduledTasks(ScheduledAnnotationBeanPostProcessor postProcessor, ApplicationContext appContext) {
        postProcessor.setApplicationContext(appContext);
        postProcessor.getScheduledTasks().forEach(ScheduledTask::cancel);   
    }
}

Use example:

import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import com.example.Utils;


@RunWith(SpringRunner.class)
@SpringBootTest
public class TestRemoveScheduller {

    
    @Autowired
    private ScheduledAnnotationBeanPostProcessor postProcessor;
    
    @Autowired
    private ApplicationContext appContext;


    @Before
    public void init(){

        //Some init variables
        
        //Remove scheduled tasks method
        Utils.removeScheduledTasks(postProcessor, appContext);
        
    }

    //Some test methods

}

Hope this helps.

Nonmetal answered 2/1, 2019 at 12:51 Comment(0)
W
2

The way that I have solved this is for spring boot applications is by disabling the @EnableScheduling configuration for the test profile:

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.scheduling.annotation.EnableScheduling;

@Configuration
@Profile({"!test"})
@EnableScheduling
public class SchedulingConfiguration {
}
Wilburwilburn answered 23/1, 2023 at 12:39 Comment(0)
Z
1

I was looking to do this in a normal class (not a unit test). I have my main Spring Boot application but needed a small utility class to do some bulk data cleanup. I wanted to use the full application context of my main app but turn off any scheduled tasks. The best solution for me was similar to Gladson Bruno:

scheduledAnnotationBeanPostProcessor.getScheduledTasks().forEach(ScheduledTask::cancel);

Another advantage of this approach is you can get a list of all scheduled tasks, and you could add logic to cancel some tasks but not others.

Zoophyte answered 27/7, 2021 at 21:25 Comment(0)
A
1

create TestTaskScheduler Bean in test class

public class TestTaskScheduler implements TaskScheduler {
    
    private static final NullScheduledFuture NULL_SCHEDULED_FUTURE = new NullScheduledFuture();
    
    @Override
    public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {
        return NULL_SCHEDULED_FUTURE;
    }

    @Override
    public ScheduledFuture<?> schedule(Runnable task, Date startTime) {
        return NULL_SCHEDULED_FUTURE;
    }

    @Override
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Date startTime, long period) {
        return NULL_SCHEDULED_FUTURE;
    }

    @Override
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, long period) {
        return NULL_SCHEDULED_FUTURE;
    }

    @Override
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Date startTime, long delay) {
        return NULL_SCHEDULED_FUTURE;
    }

    @Override
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, long delay) {
        return NULL_SCHEDULED_FUTURE;
    }
    
    private static class NullScheduledFuture implements ScheduledFuture {
        
        @Override
        public long getDelay(TimeUnit unit) {
            return 0;
        }

        @Override
        public int compareTo(Delayed o) {
            return 0;
        }

        @Override
        public boolean cancel(boolean mayInterruptIfRunning) {
            return false;
        }

        @Override
        public boolean isCancelled() {
            return false;
        }

        @Override
        public boolean isDone() {
            return false;
        }

        @Override
        public Object get() throws InterruptedException, ExecutionException {
            return null;
        }

        @Override
        public Object get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
            return null;
        }
    }
}
Anemone answered 6/4, 2022 at 8:30 Comment(1)
Or you can simply @MockBean TaskScheduler taskScheduler;Incomprehensible
B
0

I would go for TestExecutionListener, example:

public class SchedulerExecutionListener implements TestExecutionListener {

    @Override
    public void beforeTestClass(@NonNull TestContext testContext) {
        try {
            ScheduledAnnotationBeanPostProcessor schedulerProcessor = testContext.getApplicationContext().getBean(ScheduledAnnotationBeanPostProcessor.class);
            schedulerProcessor.destroy();
    } catch (Exception ignored) {
            ignored.printStackTrace();
            System.out.println(ignored.getMessage());
        }
}

And then you add it to ur testClass

@TestExecutionListeners(listeners = SchedulerExecutionListener .class, mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS)
class Test {}
Backfill answered 1/11, 2022 at 7:13 Comment(0)
H
0

My application properties are stored in application.yml so simple add ConditionalOnProperty to scheduler:

@ConditionalOnProperty(value = "scheduling.enabled", havingValue = "true", matchIfMissing = true)
@EnableScheduling

And disable develop environment in application.yml:

environments:
    development:
        scheduling:
            enabled: false
Hypesthesia answered 22/2, 2023 at 8:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.