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.
- Declare a
private static Clock
field (a class variable). Initialize it to, say,Clock.systemUTC()
orClock.systemDefaultZone()
in the declaration. - Have your static time-sensitive methods use the
Clock
just introduced. - Have a package private (default accessible) static
inject
orsetClock
method that injects aClock
into the static variable. - If desired, have an
uninject
method that sets theClock
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()
withClock.getInstance().getCalendarInstance()
. - Replace
new Date()
withClock.getInstance().newDate()
- Replace
System.currentTimeMillis()
withClock.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
How to Pass Arithmetic Operators to a Method in Java
Sorting a List of Points with Java
How to Generate the JPA Entity Metamodel
How to Schedule a Task to Run at Periodic Intervals
Write a File in Hdfs with Java
Package Conflicts with Automatic Modules in Java 9
What Is the Significance of Load Factor in Hashmap
How to Create an Instance of an Interface in Java
Best Way to Make Java's Modulus Behave Like It Should with Negative Numbers
Problem with "Scopes" of Variables in Try Catch Blocks in Java
Can a Constructor in Java Be Private
Convert from List<Completablefuture> to Completablefuture<List>
How Can a Class Have a Member of Its Own Type, Isn't This Infinite Recursion