Spring
@Scheduled
Testing
Java
Spring Boot

How to test Spring Scheduled

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

The @Scheduled annotation in Spring provides a declarative way to run tasks at fixed intervals or on cron-based schedules. Testing these tasks is often overlooked because developers assume the scheduling infrastructure itself handles correctness. However, the business logic inside scheduled methods needs thorough testing, and you may also want to verify that the scheduling configuration triggers execution at the right times.

Understanding @Scheduled Annotation

The @Scheduled annotation supports three main timing strategies. fixedRate runs the method at a constant interval regardless of how long the previous execution took. fixedDelay waits a specified duration after the previous execution finishes before starting the next one. cron uses a cron expression for calendar-based scheduling.

java
1@Component
2public class ReportGenerator {
3
4    @Scheduled(fixedRate = 60000)
5    public void generateHourlyStats() {
6        // runs every 60 seconds
7    }
8
9    @Scheduled(fixedDelay = 30000)
10    public void cleanupTempFiles() {
11        // runs 30 seconds after previous execution completes
12    }
13
14    @Scheduled(cron = "0 0 2 * * ?")
15    public void nightlyBackup() {
16        // runs at 2:00 AM every day
17    }
18}

To enable scheduling, your configuration class must include @EnableScheduling.

java
1@SpringBootApplication
2@EnableScheduling
3public class MyApplication {
4    public static void main(String[] args) {
5        SpringApplication.run(MyApplication.class, args);
6    }
7}

Unit Testing the Method Directly

The simplest and most reliable approach is to test the scheduled method as a plain method call, ignoring the scheduling mechanism entirely. This tests the business logic without waiting for timers.

java
1@ExtendWith(MockitoExtension.class)
2class ReportGeneratorTest {
3
4    @Mock
5    private ReportRepository reportRepository;
6
7    @Mock
8    private MetricsService metricsService;
9
10    @InjectMocks
11    private ReportGenerator reportGenerator;
12
13    @Test
14    void generateHourlyStats_createsReport() {
15        when(metricsService.collectStats()).thenReturn(new Stats(100, 5));
16
17        reportGenerator.generateHourlyStats();
18
19        verify(reportRepository).save(any(Report.class));
20        verify(metricsService).collectStats();
21    }
22}

This approach is fast, deterministic, and covers the most important aspect: does the method do the right thing when called?

Integration Testing with Awaitility

When you need to verify that Spring actually triggers the scheduled method, use the Awaitility library to wait for asynchronous execution without fragile Thread.sleep() calls.

First, add the Awaitility dependency.

xml
1<dependency>
2    <groupId>org.awaitility</groupId>
3    <artifactId>awaitility</artifactId>
4    <version>4.2.0</version>
5    <scope>test</scope>
6</dependency>

Then write an integration test that starts the Spring context and waits for the scheduled method to execute.

java
1@SpringBootTest
2class ReportGeneratorIntegrationTest {
3
4    @SpyBean
5    private ReportGenerator reportGenerator;
6
7    @Test
8    void scheduledMethodIsInvoked() {
9        await()
10            .atMost(Duration.ofSeconds(5))
11            .untilAsserted(() ->
12                verify(reportGenerator, atLeast(2))
13                    .generateHourlyStats()
14            );
15    }
16}

@SpyBean wraps the real bean in a Mockito spy, allowing you to verify invocation counts without changing the bean behavior.

Overriding Cron Expressions for Testing

Production cron expressions like "run at 2 AM daily" are impractical for testing. Externalize the schedule into a property so tests can override it with a fast interval.

java
1@Component
2public class NightlyBackupTask {
3
4    @Scheduled(cron = "${backup.cron:0 0 2 * * ?}")
5    public void runBackup() {
6        // backup logic
7    }
8}

In your test properties file, override the cron to run every second.

properties
# src/test/resources/application-test.properties
backup.cron=* * * * * ?
java
1@SpringBootTest
2@ActiveProfiles("test")
3class NightlyBackupTaskTest {
4
5    @SpyBean
6    private NightlyBackupTask backupTask;
7
8    @Test
9    void backupRunsOnSchedule() {
10        await()
11            .atMost(Duration.ofSeconds(3))
12            .untilAsserted(() ->
13                verify(backupTask, atLeast(2)).runBackup()
14            );
15    }
16}

This same pattern works with fixedRateString and fixedDelayString for interval-based schedules.

java
1@Scheduled(fixedRateString = "${cleanup.interval:60000}")
2public void cleanup() {
3    // cleanup logic
4}

Testing with a Custom TaskScheduler

For fine-grained control, you can replace the default task scheduler with a synchronous one in tests. This eliminates timing issues entirely.

java
1@TestConfiguration
2public class TestSchedulerConfig {
3
4    @Bean
5    @Primary
6    public TaskScheduler testTaskScheduler() {
7        return new SyncTaskScheduler();
8    }
9}

Alternatively, use a ThreadPoolTaskScheduler with a short poll interval.

java
1@TestConfiguration
2public class TestSchedulerConfig {
3
4    @Bean
5    @Primary
6    public TaskScheduler taskScheduler() {
7        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
8        scheduler.setPoolSize(1);
9        scheduler.setThreadNamePrefix("test-scheduler-");
10        scheduler.initialize();
11        return scheduler;
12    }
13}

Disabling Scheduling in Unrelated Tests

Scheduled tasks can interfere with tests that have nothing to do with scheduling. Disable them by conditionally enabling scheduling.

java
1@Configuration
2@EnableScheduling
3@Profile("!no-scheduling")
4public class SchedulingConfig {
5}

Tests that do not need scheduling activate the no-scheduling profile.

java
1@SpringBootTest
2@ActiveProfiles("no-scheduling")
3class UserServiceTest {
4    // no scheduled tasks will run during this test
5}

Common Pitfalls

  • Using Thread.sleep instead of Awaitility: Fixed sleep durations make tests slow and flaky; Awaitility polls until the condition is met or a timeout is reached, making tests both faster and more reliable.
  • Hardcoding cron expressions in the annotation: This makes it impossible to override schedules in tests; always use property placeholders like ${cron.expression} with a sensible default.
  • Forgetting @EnableScheduling: Without this annotation on a configuration class, Spring never triggers scheduled methods, and tests that rely on automatic invocation will time out silently.
  • Not using @SpyBean for verification: Creating a manual spy or mock outside the Spring context does not intercept calls made by the scheduler; @SpyBean integrates with the Spring container to wrap the actual bean.
  • Leaving scheduled tasks active in all test classes: Background scheduled methods can cause side effects in unrelated integration tests; use profile-based conditional scheduling to disable them where not needed.

Summary

  • Test the business logic of scheduled methods directly as unit tests by calling the method without Spring context.
  • Use @SpyBean with Awaitility for integration tests that verify the scheduling infrastructure actually triggers the method.
  • Externalize cron expressions and intervals into properties so tests can override them with fast values.
  • Disable scheduling with @Profile annotations in test classes that do not need it to prevent interference.
  • Prefer Awaitility over Thread.sleep for reliable, non-flaky asynchronous test assertions.

Related reading
Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.