在Spring Tests上禁用@EnableScheduling

时间:2015-03-12 15:57:05

标签: spring unit-testing junit junit4 springjunit4classrunner

当我运行单元测试时,它会调用我的计划任务。我想防止这种行为,这是因为我的主应用配置上有@EnableScheduling这一事实。

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

我遇到过这个question/answer,建议设置个人资料?

不确定我会怎么做?或者如果它有点矫枉过正?我正在考虑为我的单元测试提供单独的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);
    }
}

8 个答案:

答案 0 :(得分:36)

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

AppConfiguration添加此

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

并在测试中添加此注释以禁用调度

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

答案 1 :(得分:11)

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

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或确保PostProcessors的排序不会成为问题的可能影响...

答案 2 :(得分:6)

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

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

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

timing:
    updateData: 60000
    initialDelay: 10000000000

和主要application.yaml:

timing:
    updateData: 60000
    initialDelay: 1

不是关闭它,而是造成了如此长的延迟,测试将在运行之前很长一段时间。不是最优雅的解决方案,但绝对是我找到的最简单的解决方案之一。

答案 3 :(得分:6)

使用@MockBean,又一个解决方案,无需更改生产代码。

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

最终将替换活动的预定作业或创建模拟作业。

来自文档

可以通过类型或通过{@link #name()bean name}来注册模仿。任何现有的单身 如果没有,则在上下文中定义的相同类型的bean将替换为模拟 定义了现有的bean,将添加一个新的bean。

答案 4 :(得分: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

这样,您可以为测试配置任何设置,而不是在生产代码中。例如,您可以使用内存数据库进行测试,而不是使用常规数据库。

答案 5 :(得分:1)

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

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

    public static void removeSchedulledTasks(ScheduledAnnotationBeanPostProcessor postProcessor, ApplicationContext appContext) {

    postProcessor.setApplicationContext(appContext);

    Iterator<ScheduledTask> iterator = postProcessor.getScheduledTasks().iterator();

    while(iterator.hasNext()) {

        ScheduledTask taskAtual = iterator.next();
        taskAtual.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 schedulled tasks method
        Utils.removeSchedulledTasks(postProcessor, appContext);

    }

    //Some test methods

}

希望这会有所帮助。

答案 6 :(得分:1)

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

scheduler:
  cron-expr: '-'

另请参阅disable scheduling with '-'。 在您的调度程序类中,您可以传递表达式。

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

答案 7 :(得分:0)

我希望在普通班级(而不是单元测试)中执行此操作。我有我的主要 Spring Boot 应用程序,但需要一个小的实用程序类来进行一些批量数据清理。我想使用主应用程序的完整应用程序上下文,但关闭所有计划任务。对我来说最好的解决方案类似于 Gladson Bruno:

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

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