在 Spring 测试中禁用 @EnableScheduling
Disable @EnableScheduling on Spring Tests
当我 运行 我的单元测试时,它会调用我的计划任务。我想防止这种行为,这是由于我的主应用程序配置中有 @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);
}
}
在每个测试中,您定义应该使用哪个 spring 配置,目前您有:
@ContextConfiguration(classes={AppConfiguration.class})
通常的做法是为您的正常应用程序和测试定义单独的 spring 配置。
AppConfiguration.java
TestConfiguration.java
然后在您的测试中,您只需使用 @ContextConfiguration(classes={TestConfiguration.class})
引用 TestConfiguration
而不是当前的 AppConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={TestConfiguration.class})
@Transactional
@TransactionConfiguration(defaultRollback = true)
@WebAppConfiguration
public class ExampleDaoTest
通过这种方式,您可以为测试配置不同于生产代码的任何设置。例如,您可以使用内存数据库进行测试,而不是常规的测试等等。
另一种方法是注销安排事件的 bean post 处理器。这可以通过简单地将以下 class 放在测试的 class 路径上来完成:
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 进行排序的可能影响一个问题...
如果您不想使用配置文件,您可以添加标志以enable/disable为应用程序安排
在你的AppConfiguration
中添加这个
@ConditionalOnProperty(
value = "app.scheduling.enable", havingValue = "true", matchIfMissing = true
)
@Configuration
@EnableScheduling
public static class SchedulingConfiguration {
}
并且在您的测试中只需添加此注释以禁用调度
@TestPropertySource(properties = "app.scheduling.enable=false")
我刚刚用可配置的延迟时间参数化了我的@Scheduled 注释:
@Scheduled(fixedRateString = "${timing.updateData}", initialDelayString = "${timing.initialDelay}")
在我的测试中application.yaml:
timing:
updateData: 60000
initialDelay: 10000000000
和主要 application.yaml:
timing:
updateData: 60000
initialDelay: 1
它不是关闭它而是造成如此长的延迟,测试将在它运行之前很久就结束了。不是最优雅的解决方案,但绝对是我发现的最简单的解决方案之一。
我能够通过创建一种在单元测试期间删除计划任务的方法来解决这个问题。
这是一个例子:
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
}
希望对您有所帮助。
另一个解决方案,生产代码没有任何变化,使用 @MockBean
。
@RunWith(SpringRunner.class)
@SpringBootTest
@MockBean(MyScheduledClass.class)
public class MyTest {
最终将替换活动的预定作业或创建模拟作业。
来自文档
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.
使用 Spring 引导和 cron 表达式,您可以启用或禁用调度。
例如你可以定义一个测试 application.yml 和 set
scheduler:
cron-expr: '-'
另见 disable scheduling with '-'。
在您的调度程序中 class 您可以传递表达式。
@Scheduled(cron = "${scheduler.cron-expr}")
我希望在正常 class 中执行此操作(不是单元测试)。我有我的主要 Spring 引导应用程序,但需要一个小实用程序 class 来执行一些批量数据清理。我想使用我的主应用程序的完整应用程序上下文,但关闭所有计划任务。对我来说最好的解决方案类似于 Gladson Bruno:
scheduledAnnotationBeanPostProcessor.getScheduledTasks().forEach(ScheduledTask::cancel);
这种方法的另一个优点是您可以获得所有计划任务的列表,并且您可以添加逻辑来取消某些任务而不是其他任务。
发现添加
app.scheduling.enable=false
正在测试 application.properties 以及
@ConditionalOnProperty(value = "app.scheduling.enable", havingValue = "true", matchIfMissing = true)
@EnableScheduling
调度配置 class 像 中的注释适用于所有测试,无需对每个测试都进行注释!
在测试中创建 TestTaskScheduler
Bean 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;
}
}
}
当我 运行 我的单元测试时,它会调用我的计划任务。我想防止这种行为,这是由于我的主应用程序配置中有 @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);
}
}
在每个测试中,您定义应该使用哪个 spring 配置,目前您有:
@ContextConfiguration(classes={AppConfiguration.class})
通常的做法是为您的正常应用程序和测试定义单独的 spring 配置。
AppConfiguration.java
TestConfiguration.java
然后在您的测试中,您只需使用 @ContextConfiguration(classes={TestConfiguration.class})
TestConfiguration
而不是当前的 AppConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={TestConfiguration.class})
@Transactional
@TransactionConfiguration(defaultRollback = true)
@WebAppConfiguration
public class ExampleDaoTest
通过这种方式,您可以为测试配置不同于生产代码的任何设置。例如,您可以使用内存数据库进行测试,而不是常规的测试等等。
另一种方法是注销安排事件的 bean post 处理器。这可以通过简单地将以下 class 放在测试的 class 路径上来完成:
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 进行排序的可能影响一个问题...
如果您不想使用配置文件,您可以添加标志以enable/disable为应用程序安排
在你的AppConfiguration
中添加这个
@ConditionalOnProperty(
value = "app.scheduling.enable", havingValue = "true", matchIfMissing = true
)
@Configuration
@EnableScheduling
public static class SchedulingConfiguration {
}
并且在您的测试中只需添加此注释以禁用调度
@TestPropertySource(properties = "app.scheduling.enable=false")
我刚刚用可配置的延迟时间参数化了我的@Scheduled 注释:
@Scheduled(fixedRateString = "${timing.updateData}", initialDelayString = "${timing.initialDelay}")
在我的测试中application.yaml:
timing:
updateData: 60000
initialDelay: 10000000000
和主要 application.yaml:
timing:
updateData: 60000
initialDelay: 1
它不是关闭它而是造成如此长的延迟,测试将在它运行之前很久就结束了。不是最优雅的解决方案,但绝对是我发现的最简单的解决方案之一。
我能够通过创建一种在单元测试期间删除计划任务的方法来解决这个问题。 这是一个例子:
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
}
希望对您有所帮助。
另一个解决方案,生产代码没有任何变化,使用 @MockBean
。
@RunWith(SpringRunner.class)
@SpringBootTest
@MockBean(MyScheduledClass.class)
public class MyTest {
最终将替换活动的预定作业或创建模拟作业。
来自文档
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.
使用 Spring 引导和 cron 表达式,您可以启用或禁用调度。 例如你可以定义一个测试 application.yml 和 set
scheduler:
cron-expr: '-'
另见 disable scheduling with '-'。 在您的调度程序中 class 您可以传递表达式。
@Scheduled(cron = "${scheduler.cron-expr}")
我希望在正常 class 中执行此操作(不是单元测试)。我有我的主要 Spring 引导应用程序,但需要一个小实用程序 class 来执行一些批量数据清理。我想使用我的主应用程序的完整应用程序上下文,但关闭所有计划任务。对我来说最好的解决方案类似于 Gladson Bruno:
scheduledAnnotationBeanPostProcessor.getScheduledTasks().forEach(ScheduledTask::cancel);
这种方法的另一个优点是您可以获得所有计划任务的列表,并且您可以添加逻辑来取消某些任务而不是其他任务。
发现添加
app.scheduling.enable=false
正在测试 application.properties 以及
@ConditionalOnProperty(value = "app.scheduling.enable", havingValue = "true", matchIfMissing = true)
@EnableScheduling
调度配置 class 像
在测试中创建 TestTaskScheduler
Bean 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;
}
}
}