Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Spring scheduled worker test: inject a clock and call the work once

Last updated: 5 Oct 20264 min read
tutorial
IntermediateBy AITrove Editorial

Test a due-time rule without waiting for a real scheduler tick or relying on wall-clock races.

Separate trigger from work

@Scheduled decides when to call a method. A reminder worker decides which due rows to claim and how many to process. Put that decision in a callable service with an injected Clock; keep the scheduled adapter small. A test can advance a fixed clock and call one work cycle directly. The scheduled outbox pattern] still needs database ownership rules across replicas.

Make time a precise input

At 09:47 UTC, a reminder due exactly at 09:47 should be included if the contract says dueAt less than or equal to now. Seed one row just before, one equal, and one after the boundary. Assert a bounded batch of 47 and the resulting state. Do not use Thread.sleep or depend on the CI machine timezone. Test shutdown and draining] separately because a fixed clock does not exercise scheduler lifecycle.

Keep concurrency in another test

A direct call proves selection logic, not that two application replicas cannot claim the same row. Use an integration test with two independent workers and a real database lock or claim update for that invariant; poller integration tests] cover it. Capture failures as retryable state rather than letting a scheduled method's exception lose a work item.

Implementation contract

Java
final class ReminderWorker {
    private final Clock clock;
    private final ReminderStore reminders;

    ReminderWorker(Clock clock, ReminderStore reminders) {
        this.clock = clock;
        this.reminders = reminders;
    }

    int runOneCycle() {
        return reminders.claimDueBeforeOrAt(clock.instant(), 47).size();
    }
}

class ReminderWorkerClockTest {
    @Test void usesFixedCutoff() {
        Instant cutoff = Instant.parse("2026-10-05T09:47:00Z");
        ReminderStore store = mock(ReminderStore.class);
        when(store.claimDueBeforeOrAt(cutoff, 47)).thenReturn(List.of());
        Clock fixed = Clock.fixed(cutoff, ZoneOffset.UTC);
        assertThat(new ReminderWorker(fixed, store).runOneCycle()).isZero();
        verify(store).claimDueBeforeOrAt(cutoff, 47);
    }
}

Cost and verification

A fixed clock removes waiting and makes boundary tests fast. It does not replace a database concurrency test or a scheduler lifecycle test; those cost more because they exercise real threads and storage ownership.

Common Mistakes

  • Do not sleep until a scheduled tick in a unit test.
  • Do not let system timezone decide a UTC due-time assertion.
  • Do not mistake a single-worker fixed-clock test for multi-replica claim safety.

Read next

Spring TaskScheduler: poll committed outbox rows in the background, Spring relay integration tests: prove the boundaries H2 cannot, Spring TaskScheduler shutdown: stop new polls and account for in-flight work, Spring outbox polling: cap work per tick and measure backlog age, Test a Spring worker at admission, lease and replay boundaries.

spring
spring-boot
testing
scheduled-worker-fixed-clock-test
Storage details