How do I test code that depends on time?

Code that depends directly on the current date or time can be difficult to test because the result changes every time the test runs.

For example, code like this is hard to test reliably:

import java.time.LocalDate;

public class SubscriptionService {
    public boolean isExpired(LocalDate expiryDate) {
        return expiryDate.isBefore(LocalDate.now());
    }
}

The problem is LocalDate.now().
Today’s test result may be different from tomorrow’s.

The best solution is to avoid calling the system clock directly inside your business logic. Instead, inject a time source, usually java.time.Clock.


Using Clock in Production Code

Java’s java.time.Clock lets you control what “now” means.

import java.time.Clock;
import java.time.LocalDate;

public class SubscriptionService {
    private final Clock clock;

    public SubscriptionService(Clock clock) {
        this.clock = clock;
    }

    public boolean isExpired(LocalDate expiryDate) {
        LocalDate today = LocalDate.now(clock);
        return expiryDate.isBefore(today);
    }
}

Now the class no longer depends directly on the real system date.
Instead, it depends on a Clock.


Using the Real Clock in Application Code

In normal application code, pass the system clock:

import java.time.Clock;

public class Application {
    public static void main(String[] args) {
        SubscriptionService service =
                new SubscriptionService(Clock.systemDefaultZone());

        // Use the service normally
    }
}

You can also use UTC if your application should not depend on the server’s local timezone:

Clock.systemUTC()

Testing with a Fixed Clock

In tests, use Clock.fixed(...) to make time predictable.

import org.junit.jupiter.api.Test;

import java.time.Clock;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

class SubscriptionServiceTest {

    @Test
    void isExpiredReturnsTrueWhenExpiryDateIsBeforeToday() {
        Clock fixedClock = Clock.fixed(
                Instant.parse("2026-07-13T10:00:00Z"),
                ZoneId.of("UTC")
        );

        SubscriptionService service = new SubscriptionService(fixedClock);

        boolean expired = service.isExpired(LocalDate.of(2026, 7, 12));

        assertTrue(expired);
    }

    @Test
    void isExpiredReturnsFalseWhenExpiryDateIsToday() {
        Clock fixedClock = Clock.fixed(
                Instant.parse("2026-07-13T10:00:00Z"),
                ZoneId.of("UTC")
        );

        SubscriptionService service = new SubscriptionService(fixedClock);

        boolean expired = service.isExpired(LocalDate.of(2026, 7, 13));

        assertFalse(expired);
    }

    @Test
    void isExpiredReturnsFalseWhenExpiryDateIsAfterToday() {
        Clock fixedClock = Clock.fixed(
                Instant.parse("2026-07-13T10:00:00Z"),
                ZoneId.of("UTC")
        );

        SubscriptionService service = new SubscriptionService(fixedClock);

        boolean expired = service.isExpired(LocalDate.of(2026, 7, 14));

        assertFalse(expired);
    }
}

Now the tests are stable because “today” is always 2026-07-13.


Testing Date and Time Logic

The same approach works with LocalDateTime, ZonedDateTime, Instant, and other classes from the java.time package.

Example:

import java.time.Clock;
import java.time.LocalDateTime;

public class GreetingService {
    private final Clock clock;

    public GreetingService(Clock clock) {
        this.clock = clock;
    }

    public String greeting() {
        int hour = LocalDateTime.now(clock).getHour();

        if (hour < 12) {
            return "Good morning";
        }

        if (hour < 18) {
            return "Good afternoon";
        }

        return "Good evening";
    }
}

Test:

import org.junit.jupiter.api.Test;

import java.time.Clock;
import java.time.Instant;
import java.time.ZoneId;

import static org.junit.jupiter.api.Assertions.assertEquals;

class GreetingServiceTest {

    @Test
    void greetingReturnsGoodMorningBeforeNoon() {
        Clock fixedClock = Clock.fixed(
                Instant.parse("2026-07-13T08:00:00Z"),
                ZoneId.of("UTC")
        );

        GreetingService service = new GreetingService(fixedClock);

        assertEquals("Good morning", service.greeting());
    }

    @Test
    void greetingReturnsGoodAfternoonAfterNoon() {
        Clock fixedClock = Clock.fixed(
                Instant.parse("2026-07-13T14:00:00Z"),
                ZoneId.of("UTC")
        );

        GreetingService service = new GreetingService(fixedClock);

        assertEquals("Good afternoon", service.greeting());
    }

    @Test
    void greetingReturnsGoodEveningInTheEvening() {
        Clock fixedClock = Clock.fixed(
                Instant.parse("2026-07-13T20:00:00Z"),
                ZoneId.of("UTC")
        );

        GreetingService service = new GreetingService(fixedClock);

        assertEquals("Good evening", service.greeting());
    }
}

Using Clock with Spring

In a Spring application, you can register a Clock as a bean.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.time.Clock;

@Configuration
public class TimeConfig {

    @Bean
    public Clock clock() {
        return Clock.systemUTC();
    }
}

Then inject it into your service:

import org.springframework.stereotype.Service;

import java.time.Clock;
import java.time.LocalDate;

@Service
public class SubscriptionService {
    private final Clock clock;

    public SubscriptionService(Clock clock) {
        this.clock = clock;
    }

    public boolean isExpired(LocalDate expiryDate) {
        return expiryDate.isBefore(LocalDate.now(clock));
    }
}

In tests, replace the Clock bean with a fixed one:

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;

import java.time.Clock;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;

import static org.junit.jupiter.api.Assertions.assertTrue;

class SubscriptionServiceSpringTest {

    @TestConfiguration
    static class FixedClockConfig {
        @Bean
        Clock clock() {
            return Clock.fixed(
                    Instant.parse("2026-07-13T10:00:00Z"),
                    ZoneId.of("UTC")
            );
        }
    }

    @Test
    void isExpiredUsesFixedClock() {
        SubscriptionService service =
                new SubscriptionService(FixedClockConfig.class.cast(null));
    }
}

A simpler unit test usually does not need Spring at all:

import org.junit.jupiter.api.Test;

import java.time.Clock;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;

import static org.junit.jupiter.api.Assertions.assertTrue;

class SubscriptionServiceTest {

    @Test
    void isExpiredUsesFixedClock() {
        Clock fixedClock = Clock.fixed(
                Instant.parse("2026-07-13T10:00:00Z"),
                ZoneId.of("UTC")
        );

        SubscriptionService service = new SubscriptionService(fixedClock);

        assertTrue(service.isExpired(LocalDate.of(2026, 7, 12)));
    }
}

Avoid These Approaches When Possible

Avoid scattering calls like these throughout your code:

LocalDate.now();
LocalDateTime.now();
Instant.now();
System.currentTimeMillis();
new Date();

They make tests harder because the current time cannot easily be controlled.

Also avoid adding arbitrary sleeps to tests:

Thread.sleep(1000);

Tests that depend on sleeping are usually slow and unreliable.


Good Rule of Thumb

Use this rule:

If your code needs the current time, inject a Clock.

This makes your code:

  • easier to test
  • more predictable
  • less dependent on the machine’s timezone
  • easier to reason about
  • safer around midnight and date-boundary bugs

For most Java applications, java.time.Clock is the cleanest way to test code that depends on time.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.