在Spring Boot IntegrationTest上禁用@Schedule

问题描述 投票:14回答:6

如何在Spring Boot IntegrationTest上禁用计划自动启动?

谢谢。

java spring spring-boot automated-tests spring-scheduled
6个回答
17
投票

请注意,外部组件可能会自动启用调度(请参阅Spring Framework中的HystrixStreamAutoConfigurationMetricExportAutoConfiguration)。因此,如果您尝试在指定@ConditionalOnProperty@Profile类上使用@Configuration@EnableScheduling,则无论如何都会因外部组件而启用调度。

一个解决方案

有一个@Configuration类可以通过@EnableScheduling进行调度,但是然后将你的预定作业放在不同的类中,每个类使用@ConditionalOnProperty来启用/禁用包含@Scheduled任务的类。

不要在同一个类中使用@Scheduled@EnableScheduling,否则无论如何都会遇到外部组件启用它的问题,因此忽略@ConditionalOnProperty

例如:

@Configuration
@EnableScheduling
public class MyApplicationSchedulingConfiguration {
}

然后在另一个班级

@Named
@ConditionalOnProperty(value = "scheduling.enabled", havingValue = "true", matchIfMissing = false)
public class MyApplicationScheduledTasks {

  @Scheduled(fixedRate = 60 * 60 * 1000)
  public void runSomeTaskHourly() {
    doStuff();
  }
}

此解决方案的问题是每个预定的作业都需要在其自己的类中指定@ConditionalOnProperty。如果您错过了该注释,那么该作业将会运行。

另一种方案

扩展ThreadPoolTaskScheduler并覆盖TaskScheduler方法。在这些方法中,您可以执行检查以查看作业是否应该运行。

然后,在使用@EnableScheduling的@Configuration类中,还创建一个名为taskScheduler的@Bean,它返回自定义线程池任务调度程序。

例如:

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

使用我们的自定义调度程序创建taskScheduler bean的配置类,并启用调度

@Configuration
@EnableScheduling
public class MyApplicationSchedulingConfiguration {

  @Bean
  public TaskScheduler taskScheduler() {
    return new ConditionalThreadPoolTaskScheduler();
  }
}

上面的潜在问题是你已经创建了对内部Spring类的依赖,所以如果将来有变化,你必须修复兼容性。


3
投票

我有同样的问题。尝试使用我的调度Bean测试Spring的@ConditionalOnProperty属性,但调度仍然在测试中被激活。

我找到的唯一好的解决方法是覆盖Test类中的调度属性,这样作业就没有真正的运行机会了。

如果您的真实工作每5分钟运行一次,请使用my.cron=0 0/5 * * * *

public class MyJob {

    @Scheduled(cron = "${my.cron}")
    public void execute() {
        // do something
    }
} 

然后在测试类中,您可以将其配置为:

@RunWith(SpringRunner.class)
@SpringBootTest(properties = {"my.cron=0 0 0 29 2 ?"}) // Configured as 29 Feb ;-)
public class MyApplicationTests {

    @Test
    public void contextLoads() {
    }

}

因此,即使您的工作被激活,它也只会在2月29日的第0小时运行,这在4年内发生一次。所以你运行它的机会很小。

您可以提供更多精美的cron设置以满足您的要求。


1
投票

当你真正的Spring Boot Application类看起来像这样:

@SpringBootApplication   
@EnableScheduling
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }

}

您将不得不为没有@EnableScheduling创建另一个Application类来进行集成测试,如下所示:

@SpringBootApplication   
public class MyTestApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyTestApplication.class, args);
    }

}

然后在集成测试中使用MyTestApplication类

RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = MyTestApplication.class)
public class MyIntegrationTest {

...
}

这就是我这样做的方式,因为我没有找到更好的方法。


1
投票

我在Spring Boot 2.0.3中找到了一个简单的解决方案:

1)将预定方法提取到单独的bean

@Service
public class SchedulerService {

  @Autowired
  private SomeTaskService someTaskService;

  @Scheduled(fixedRate = 60 * 60 * 1000)
  public void runSomeTaskHourly() {
    someTaskService.runTask();
  }
}

2)模拟测试类中的调度程序bean

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

  @Autowired
  private SomeTaskService someTaskService;

  @MockBean
  private SchedulerService schedulerService;
}

1
投票

一种方法是使用Spring配置文件

在您的测试类中:

@SpringBootTest(classes = Application.class)
@ActiveProfiles("integration-test")
public class SpringBootTestBase {
    ...
}

在您的调度程序类或方法中:

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

    ...
}

0
投票

我通过使用单独的配置类解决了这个问题,然后在测试上下文中覆盖了这个类。因此,不是将注释放在应用程序中,而是将其放在单独的配置类中。 正常背景:

@Configuration
@EnableScheduling 
public class SpringConfiguration {}

测试环境:

@Configuration
public class SpringConfiguration {}

0
投票

整合上面的一些答案:

  • 为测试目的创建一个单独的配置类(a.k.a“TestConfiguration.class”)
  • 在那里为其他bean启用Mockito注释:调度程序等.- Read this: 2) @ConditionalOnClass @ConditionalOnMissingBean @ConditionalOnBean @ConditionalOnJava @ConditionalOnJndi @ConditionalOnMissingClass @ConditionalOnExpression @ConditionalOnNotWebApplication @ConditionalOnWebApplication @ConditionalOnProperty @ConditionalOnResource @ConditionalOnSingleCandidate

另外,请务必检查:

  • “application.yml”自我创建属性取决于外部设备/服务
  • Main类上的自动配置注释破坏bean初始化序列
  • “applicationContext.xml”,“beans.xml”或Classpath加载器

请阅读以下内容:

© www.soinside.com 2019 - 2024. All rights reserved.