在 Spring 测试中禁用 @EnableScheduling

问题描述 投票:0回答:13

当我运行单元测试时,它会调用我的计划任务。我想防止这种行为,这是由于我的主应用程序配置中有

@EnableScheduling
导致的。

如何在单元测试中禁用此功能?

我遇到过这个问题/答案,建议设置配置文件?

不确定我该怎么做?或者是否太过分了?我正在考虑为我的单元测试使用一个单独的 AppConfiguration,但当我这样做时感觉就像重复代码两次?

@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

}

单元测试示例。

@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);
    }
}
spring unit-testing junit junit4 springjunit4classrunner
13个回答
109
投票

如果您不想使用配置文件,您可以添加启用/禁用应用程序调度的标志

在您的

AppConfiguration
中添加此

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

在您的测试中只需添加此注释即可禁用调度

@TestPropertySource(properties = "app.scheduling.enable=false")

37
投票

我刚刚用可配置的延迟时间参数化了我的 @Scheduled 注释:

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

在我的测试应用程序.yaml 中:

timing:
    updateData: 60000
    initialDelay: 10000000000

和主应用程序.yaml:

timing:
    updateData: 60000
    initialDelay: 1

它不是将其关闭,而是造成如此长的延迟,测试在运行之前就已经结束了。这不是最优雅的解决方案,但绝对是我发现的最简单的解决方案之一。


30
投票

又一个解决方案,无需对生产代码进行任何更改,使用

@MockBean

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

这最终将取代活动的计划作业或创建一个模拟作业。

来自文档

Mock 可以通过类型或 {@link #name() bean name} 来注册。任何现有的单曲 上下文中定义的相同类型的bean将被mock替换,如果没有 现有的 bean 已定义,将添加新的 bean。


21
投票

另一种方法是取消注册安排事件的 bean 后处理器。这可以通过简单地将以下类放在测试的类路径中来完成:

@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);
        }
    }
}

虽然这很简单并且似乎可以完成这项工作,但请注意,我没有对此进行太多测试,也没有检查从注册表中删除定义的 bean 或确保后处理器的排序不会成为问题的可能影响。 .


17
投票

使用 Spring Boot 和 cron 表达式,您可以启用或禁用调度。 例如,您可以定义一个测试 application.yml 并设置

scheduler:
  cron-expr: '-'

另请参阅使用“-”禁用计划。 在您的调度程序类中,您可以传递表达式。

@Scheduled(cron = "${scheduler.cron-expr}")

5
投票

发现添加了

app.scheduling.enable=false

在测试应用程序中。属性以及

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

Marko Vranjkovic 的答案那样安排配置类注释适用于所有测试,无需对每个测试进行注释!


2
投票

在每个测试中,您定义应使用哪种弹簧配置,目前您有:

@ContextConfiguration(classes={AppConfiguration.class})

常见的做法是为正常应用程序和测试定义单独的弹簧配置。

AppConfiguration.java 
TestConfiguration.java

然后在测试中,您只需使用

TestConfiguration
 引用 
AppConfiguration
 而不是当前的 
@ContextConfiguration(classes={TestConfiguration.class})

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

通过这种方式,您可以以不同于生产代码的方式为测试配置任何设置。例如,您可以使用内存数据库进行测试,而不是常规数据库等等。


2
投票

我能够通过创建一种在单元测试期间删除计划任务的方法来解决这个问题。 这是一个例子:

    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);   
    }
}

使用示例:

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

}

希望这有帮助。


2
投票

我为 Spring Boot 应用程序解决此问题的方法是禁用测试配置文件的 @EnableScheduling 配置:

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

1
投票

我希望在普通课堂(而不是单元测试)中做到这一点。我有我的主要 Spring Boot 应用程序,但需要一个小型实用程序类来进行一些批量数据清理。我想使用主应用程序的完整应用程序上下文,但关闭所有计划任务。对我来说最好的解决方案类似于格拉德森布鲁诺:

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

这种方法的另一个优点是您可以获得所有计划任务的列表,并且可以添加逻辑来取消某些任务,但不能取消其他任务。


1
投票

在测试类中创建

TestTaskScheduler
Bean

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;
        }
    }
}

0
投票

我会选择 TestExecutionListener, 示例:

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());
        }
}

然后将其添加到你的测试类中

@TestExecutionListeners(listeners = SchedulerExecutionListener .class, mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS)
class Test {}

0
投票

我的应用程序属性存储在

application.yml
中,因此只需将 ConditionalOnProperty 添加到调度程序即可:

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

并在

application.yml
中禁用开发环境:

environments:
    development:
        scheduling:
            enabled: false
© www.soinside.com 2019 - 2024. All rights reserved.