Unit Testing a Class with a Java 8 Clock

Unit testing a class with a Java 8 Clock

I don't want to add a Clock argument to the method because the real code shouldn't be concerned with passing in a clock.

No... but you might want to consider it as a constructor parameter. Basically you're saying that your class needs a clock with which to work... so that's a dependency. Treat it as you would any other dependency, and inject it either in a constructor or via a method. (I personally favour constructor injection, but YMMV.)

As soon as you stop thinking of it as something you can easily construct yourself, and start thinking of it as "just another dependency" then you can use familiar techniques. (I'm assuming you're comfortable with dependency injection in general, admittedly.)

Writing and testing convenience methods using Java 8 Date/Time classes

In your test (and only during test!) set the clock that your convenience class uses so that you can predict the desired/expected results independently of the computer clock:

public class ConvenienceTest {

@Test
public void testGetLocalHour() {
Convenience underTest = new Convenience();

ZoneId zone = ZoneId.of("America/Los_Angeles");

ZonedDateTime fixedZdt = ZonedDateTime.now(zone).withHour(0);
underTest.setClock(Clock.fixed(fixedZdt.toInstant(), zone));
assertEquals("24", underTest.getLocalHour24HourClock());

fixedZdt = fixedZdt.withHour(1);
underTest.setClock(Clock.fixed(fixedZdt.toInstant(), zone));
assertEquals("01", underTest.getLocalHour24HourClock());

fixedZdt = fixedZdt.withHour(23);
underTest.setClock(Clock.fixed(fixedZdt.toInstant(), zone));
assertEquals("23", underTest.getLocalHour24HourClock());

// TODO test with other time zones
}

}

For this to work it of course requires that your convenience class can accept a Clock and uses it:

public class Convenience {

private Clock clock = Clock.systemDefaultZone();

/** For testing only. Sets the clock from which to get the time. */
void setClock(Clock clockForTest) {
this.clock = clockForTest;
}

public String getLocalHour24HourClock() {
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("kk");
ZonedDateTime zdt = ZonedDateTime.now(clock);
return zdt.format(fmt);
}

}

With this implementation the tests just passed on my computer.

Stepping a step back: if your convenience methods are but a thin layer on top of java.time, you may start to consider how much value a unit test has. If it is doing some real work (like formatting in the above example, something that regularly goes wrong), a test is valuable, but if a method just returns a value from a single call into java.time, you may not need a test. You shouldn’t test java.time, preferably only your own code.

Mocking time in Java 8's java.time API

The closest thing is the Clock object. You can create a Clock object using any time you want (or from the System current time). All date.time objects have overloaded now methods that take a clock object instead for the current time. So you can use dependency injection to inject a Clock with a specific time:

public class MyBean {
private Clock clock; // dependency inject
...
public void process(LocalDate eventDate) {
if (eventDate.isBefore(LocalDate.now(clock)) {
...
}
}
}

See Clock JavaDoc for more details

Static Utility Class, Unit Testing and Java.Time

There is nothing keeping you from injecting a Clock into a class containing static utility methods.

  1. Declare a private static Clock field (a class variable). Initialize it to, say, Clock.systemUTC() or Clock.systemDefaultZone() in the declaration.
  2. Have your static time-sensitive methods use the Clock just introduced.
  3. Have a package private (default accessible) static injector setClock method that injects a Clock into the static variable.
  4. If desired, have an uninject method that sets the Clock back to its initial value.

Keep your unit tests in the same package so that they may use the package private inject method. As you may have figured out, a test method testing a static utility method first injects its desired Clock, then calls the method to test and validates the return value from the method. Since the Clock is static, tests cannot run in parallel.

How to unit test a time related method?

At some point you have to register the timestamp of individual requests to see of they are > 1 and spans for less than 1 second.

In runtime, you will use eg system clock, Instant.now() or whatever. In unit tests, you will set those timestamps manually. Something like (conceptual only):

t0=Instant.now();
for(int i=1;i++;i<=10){
yourService.handleRequest(request,t0.plusMilis(i*10));
assertThat(yourSrvice.canMakeRequest).isTrue();
}
yourService.handleRequest(request,t0.plusMilis(300));
assertThat(yourSrvice.canMakeRequest).isFalse();

Now you are free to simulate timelapse by setting request timestamps at will. That is just one of possibilities.

How to unit test a time dependent method with JUnit

I would build a unix timestamp in my unitTest based on the current time. The granularity of the result of the method is coarse enough that a few millisecond difference between when you create the argument and when this method is executed will not be material. Granted, you will need to steer clear of the boundary conditions, i.e., a unixTime at 60 minutes and 24 hours.

public void testMinutes() {
Instant minutesAgo = Instant.ofEpochMilli(System.currentTimeMillis() - 15 * 60 * 1000);
String result = getAge(minutesAgo.getEpochSecond());
assertSomething...
}

And so on for hours and days tests.

Time dependent unit tests

Joda time supports setting a "fake" current time through the setCurrentMillisFixed and setCurrentMillisOffset methods of the DateTimeUtils class.

See https://www.joda.org/joda-time/apidocs/org/joda/time/DateTimeUtils.html

Override Java System.currentTimeMillis for testing time sensitive code

I strongly recommend that instead of messing with the system clock, you bite the bullet and refactor that legacy code to use a replaceable clock. Ideally that should be done with dependency injection, but even if you used a replaceable singleton you would gain testability.

This could almost be automated with search and replace for the singleton version:

  • Replace Calendar.getInstance() with Clock.getInstance().getCalendarInstance().
  • Replace new Date() with Clock.getInstance().newDate()
  • Replace System.currentTimeMillis() with Clock.getInstance().currentTimeMillis()

(etc as required)

Once you've taken that first step, you can replace the singleton with DI a bit at a time.

How do I simulate the passage of time with java.time.Clock?

Your FakeClock approach is fine. That's the approach I'd use.

If you prefer to use an implementation from a library instead of your own, you could use MutableClock from the threeten-extra library. That class was designed for exactly this use case.

The "immutable" requirement was removed from the Clock class in Java 17. It was removed not because of changes being made to Clock's implementation, but because the requirement was harmfully restrictive (see your use case) while not serving a useful purpose. So if you're using Java <17, I'd say it's safe to simply ignore that requirement.



Related Topics



Leave a reply



Submit