Skip to content

Latest commit

 

History

History
114 lines (86 loc) · 7.16 KB

README.md

File metadata and controls

114 lines (86 loc) · 7.16 KB

🕰️ Clocky

Run tests SonarCloud quality gate SonarCloud vulnerability count SonarCloud technical debt Dependabot Status Mutation testing badge Maven Central

🚀 TL;DR

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.

📖 The longer story

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.

📦️ Default implementations

The Clock class is an abstract class, and Java ships with a few implementations:

  • SystemClock, returned by Clock.system(), Clock.systemDefaultZone() and Clock.systemUTC(). This clock returns the current instant using best available system clock, usually by calling System.currentTimeInMillis. This is the type that you would typically use in your application.
  • FixedClock, returned by Clock.fixed(). As the name suggests, this clock always returns the same instant.
  • OffsetClock, returned by Clock.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 by Clock.tick(), Clock.tickMinutes() and Clock.tickSeconds(). This clock returns instants from the specified clock truncated to the nearest occurrence of the specified duration.

🧪 Testing

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

⚖️ License

Clocky is licensed under the Apache License, version 2. See LICENSE for the full text of the license.

🛠️ Contributing

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.