Test a @Scheduled function in Spring-boot with cron property
Asked Answered
A

3

8

I would like to test if my Cron-job will be executed at the given time (1 January 00:00 of each year). Is there a way to test this? Unfortunately every example I could found online, refers to jobs that will be executed after a fix period e.g. all 5 seconds.(https://www.baeldung.com/spring-testing-scheduled-annotation).

    @Scheduled(cron = " 0 0 1 1 *")
    public void myFunc() {
         do something
    }
Acetyl answered 17/11, 2020 at 17:58 Comment(1)
This post might be helpful: #17328456Autacoid
A
15

Well if you really want to test the execution you will have to spend new years eve monitoring the application ;<)

If you want to test that the scheduling has been setup properly and that your task is scheduled then you can autowire the ScheduledStaskHolder and query it for your task. Assuming this implementation:

package nl.benooms.scheduledannotationplayground;
@Service
public class FixedDateSchedule {
    @Scheduled(cron = "0 0 0 1 1 *")
    public void OnceAYearCron() {
        System.out.println("yearly");
    }
}

Then the test could be:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
class FixedDateScheduleTest {

    @Autowired
    private ScheduledTaskHolder scheduledTaskHolder;

    @Test
    public void testYearlyCronTaskScheduled() {
        Set<ScheduledTask> scheduledTasks = scheduledTaskHolder.getScheduledTasks();
        scheduledTasks.forEach(scheduledTask -> scheduledTask.getTask().getRunnable().getClass().getDeclaredMethods());
        long count = scheduledTasks.stream()
                .filter(scheduledTask -> scheduledTask.getTask() instanceof CronTask)
                .map(scheduledTask -> (CronTask) scheduledTask.getTask())
                .filter(cronTask -> cronTask.getExpression().equals("0 0 0 1 1 *") && cronTask.toString().equals("nl.benooms.scheduledannotationplayground.FixedDateSchedule.OnceAYearCron"))
                .count();
        assertThat(count).isEqualTo(1L);
    }
}

Test will fail if:

  • @Scheduled or @EnableScheduling is not set
  • the cron expression is not exact
  • the class and method name don't correspond Please note that I would format the test in a more readable format if not a sample
Andra answered 19/11, 2020 at 23:44 Comment(0)
D
1

Think about what you actually want to test.

  1. I think you should not test whether @Scheduled is doing what it is meant to do. This test should be part of the spring framework and you should not care for testing the framework.

  2. If you apply @Scheduled correctly could be a meaningful test and a solution is provided by Ben Ooms answer. However, it is very likely that you applied it correctly and once you run your app in dev mode it is very likely to see that it works or not.

  3. More Important Testing if your cron specification is correctly modeling the domain needs.

    a) It's more important from my perception, because it is easy to mess it up, because it does not have explicit notation for each value and is different from UNIX cron jobs. A test could take the specified annotation value and check if it models the domain requirements, i.e. that your 0 0 1 1 *-String actually means executed at 1 January 00:00 of each year.

    b) Another approach to testing this can be to manipulate the Clock during a test to set it explicitly to the expected time and see if the scheduler triggers the method. However, this might not catch all cases - especially not the false-positives (i.e. the scheduler might trigger at the expected moment, but also triggers at other moments where it should not).

  4. More Important: Testing that the scheduled method actually does what it should. This can be a simple unit test but might involve integration tests depending on the domain requirements. The unit test is completely independent of the scheduling mechanics and hence can be implemented as any other unit test (e.g. using a mockito runner).

I think 3) might be critical or not so important depending on the use case while 4) is always very important. Hence, both should probably be part of the testing strategy. On the other hand, 1) should not be tested and 2) is nice, but probably is over engineering the test.

Dinitrobenzene answered 2/9, 2021 at 22:42 Comment(0)
O
0

I was trying to do exactly what Ben Ooms suggested and it wasn't working for me.

I hadn't set my scheduled tasks using the annotation, I had implemented a workaround to get them scheduled using an autowired "TaskScheduler" and I wasn't able to find my scheduled tasks in the ScheduledTaskHolder, although it was working.

Turns out I had to declare a Bean like:

@Configuration
class SchedulerConfiguration {
    @Bean
    fun threadPoolTaskScheduler(): TaskScheduler {
        val scheduler = ThreadPoolTaskScheduler()
        scheduler.poolSize = 20
        return scheduler
    }
}

And schedule my tasks using an interface: SchedulingConfigurer:

class MySchedulers(
    private val myService: MyService,
    @Qualifier("threadPoolTaskScheduler") private val threadPoolTaskScheduler: TaskScheduler
) : SchedulingConfigurer {

    private val logger = Logger(this::class.java)
    private val cronExpression = "0 0 10 * * *"

    override fun configureTasks(taskRegistrar: ScheduledTaskRegistrar) {
        taskRegistrar.setTaskScheduler(threadPoolTaskScheduler)

        logger.info("Setting scheduler cron=$cronExpression")
        taskRegistrar.addCronTask(CronTask({ runMyTask() }, CronTrigger(cronExpression)))
        }
    }

    private fun runMyTask() {
        myService.update()
    }
}

And then I could successfully find my scheduled tasks using ScheduledTaskHolder as suggested by Ben Ooms.

Omasum answered 20/8, 2021 at 8:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.