Clocky is a test stub for the java.time.Clock
class introduced with JSR-310 in Java 8.
It lets you control how time flies in your tests.
Starting with Java 8, Java has a new class: java.time.Clock
.
This class provides access to the current instant, date and time using a time-zone.
Previously, Java programmers would use System.currentTimeInMillis
.
Since that is a static method, it's hard to replace it with a stub for testing purposes.
The Clock
class solves this by providing an instance method, millis
.
It is equivalent - and in fact delegates to - calling System.currentTimeInMillis
.
Apart from millis()
, the Clock
class provides other valuable methods such as instant()
which returns the same value, wrapped in an instance of Instant
.
Since millis()
and instant()
are both instance methods of the Clock
class, it becomes easier to replace those calls with a test stub.
The Clock
class is an abstract class, and Java ships with a few implementations:
SystemClock
, returned byClock.system()
,Clock.systemDefaultZone()
andClock.systemUTC()
. This clock returns the current instant using best available system clock, usually by callingSystem.currentTimeInMillis
. This is the type that you would typically use in your application.FixedClock
, returned byClock.fixed()
. As the name suggests, this clock always returns the same instant.OffsetClock
, returned byClock.offset()
. This clock adds an offset to an underlying clock - which is why you need a second clock to act as the "base" time.TickClock
, returned byClock.tick()
,Clock.tickMinutes()
andClock.tickSeconds()
. This clock returns instants from the specified clock truncated to the nearest occurrence of the specified duration.
When it comes to testing, often it doesn't matter what the exact time is during a test. But there are cases when it matters a lot. Let's say you have code that measures how long a method invocation takes - useful for monitoring purposes.
final long start = System.currentTimeInMillis();
// ... actual method invocation
final long end = System.currentTimeInMillis();
final long duration = end - start;
In such a case, you want to control exactly how much time passes between the two invocations of System.currentTimeInMillis
.
If we add an instance variable of type Clock
, and make sure to provide for an implementation, we could rewrite that code:
final Instant start = clock.instant();
// ... actual method invocation
final Instant end = clock.instant();
final Duration duration = Duration.between(start, end);
It's clear to see that this code is way easier to test than the previous version.
Unfortunately, the default implementations of Clock
do not include a version that is suitable for this scenario.
- A Fixed Clock doesn't progress, so it's unsuitable.
- The System Clock does progress, but it's not controllable. This means you cannot predict the value of
duration
. - The Offset Clock and the Tick Clock are both based on another clock, so they need either of the above, which are both unsuitable.
This is where Clocky 🕰️ comes in.
Clocky gives you a Clock
that you control very precisely from your tests.
final long base = System.currentTimeMillis(); // could be any value
final AtomicReference<Instant> instant = new AtomicReference<>();
final Clock clock = new ManualClock(instant::get);
// create system under test, passing clock along.
// invoke system under test
instant.set(Instant.ofEpochMilli(base));
// invoke system under test
instant.set(Instant.ofEpochMilli(base + 10));
// invoke system under test
// verify system under test to see the duration is indeed 10 millis
Additionally, Clocky provides the AdvanceableTime
utility for incrementally controlling time in your tests.
This also guarantees that time is always incremental, since not just any Instant
can be provided.
final AdvanceableTime time = new AdvanceableTime(Instant.EPOCH); // can start at any Instant
final Clock clock = new ManualClock(time);
// create system under test, passing clock along.
// invoke system under test
time.advanceBy(Duration.ofMillis(10));
// invoke system under test
// verify system under test to see the duration is indeed 10 millis
Clocky is licensed under the Apache License, version 2. See LICENSE for the full text of the license.
Do you have an idea for Clocky, or want to report a bug? All contributions are welcome! Feel free to file an issue with your idea, question or whatever it is you want to contribute.