Highly reliable Go code - use clocks not time
Q: How can you tell if someone is a programmer?
A: Easy! Say “time zones” and see if they flinch!
Time and clocks always sound straightforward when you don’t think too hard about them. Unfortunately for us, the job of a programmer is to think hard about the things they are building. And one of the hardest things to get right is testing code that depends on time.
Imagine you have a cache that expires items after an hour, or a retry mechanism that waits 30 seconds between attempts. How do you test these without waiting? Your unit tests would take forever to run!
When working with Go, you often see time.Sleep()
and time.Now()
.
In C the equivalents might be sleep()
or usleep()
and time()
.
In C++ you should probably use std::this_thread::sleep_for()
and system_clock::now()
.
In Python it’s datetime.datetime.now()
and time.sleep()
.
In all of these functions, you consult data derived from the OS-managed hardware clock. Because that clock is
managed outside of your code, you don’t control it! Worse, you are up a creek when it comes to testing.
I would like to propose that, instead of using these global functions, you define an interface called Clock
and
provide at least two implementations: SystemClock
and StaticClock
.
In Go this would be:
package clocks
type Clock interface {
Now() time.Time
Sleep(time.Duration)
}
type SystemClock struct{}
func (SystemClock) Now() time.Time { return time.Now() }
func (SystemClock) Sleep(d time.Duration) { time.Sleep(d) }
type StaticClock struct {
Time time.Time
}
func (sc *StaticClock) Now() time.Time { return sc.Time }
func (sc *StaticClock) Sleep(d time.Duration) { sc.Time += d }
With these two implementations, you can embed the clock in your code you want to test.
Which means that you can cause your code to use the static clock! All of its sleeps will be
instantaneous - you can verify using Now()
that time has passed, but you will never have
to wait for a Sleep()
to complete. They have been transformed into additions!
To use this, you’ll need to change the objects you have that use time to have another class member - the clock. Then at test time you can pass in a StaticClock and at runtime you can pass in the SystemClock. By testing with clock objects, you can make your unit tests both faster and significantly less flaky.
Just for completeness, here’s clocks in Python:
import datetime, time
class SystemClock(object):
def now(self) -> datetime.datetime:
return datetime.datetime.now()
def sleep(self, seconds: float):
time.sleep(seconds)
class StaticClock(object):
def __init__(self, currentTime: datetime.datetime):
self.currentTime = currentTime
def now(self) -> datetime.datetime:
return self.currentTime
def sleep(self, seconds: float):
self.currentTime += datetime.timedelta(seconds=seconds)
Using these clocks, you can decide whether you want the OS to be in control of your code’s perception of time, or whether you want to be in control. I find that in unit tests I almost always want to be in control, while in production I want to hand control to the OS.
This is called “dependency injection” when generalized, and can be of great help in many contexts. In my opinion, clocks are one of the very best dependencies to inject.
A Practical Example
Let’s look at a real-world example. Suppose you have a cache that needs to expire items after a certain duration:
type Cache struct {
clock Clock
items map[string]item
}
type item struct {
value string
expiresAt time.Time
}
func NewCache(clock Clock) *Cache {
return &Cache{
clock: clock,
items: make(map[string]item),
}
}
func (c *Cache) Set(key string, value string, ttl time.Duration) {
c.items[key] = item{
value: value,
expiresAt: c.clock.Now().Add(ttl),
}
}
func (c *Cache) Get(key string) (string, bool) {
item, exists := c.items[key]
if !exists {
return "", false
}
if c.clock.Now().After(item.expiresAt) {
return "", false
}
return item.value, true
}
Now testing this cache becomes trivial:
func TestCacheExpiration(t *testing.T) {
clock := &StaticClock{Time: time.Now()}
cache := NewCache(clock)
cache.Set("key", "value", 5*time.Minute)
// Advance time by 4 minutes
clock.Sleep(4 * time.Minute)
value, exists := cache.Get("key")
if !exists {
t.Error("Item should still exist")
}
// Advance time by 2 more minutes
clock.Sleep(2 * time.Minute)
_, exists = cache.Get("key")
if exists {
t.Error("Item should have expired")
}
}
This test runs instantly, is deterministic, and clearly demonstrates the cache’s behavior. Without StaticClock, this test would take 6 minutes!
In production, you would use:
cache := NewCache(&SystemClock{})
Key Benefits
- Testability: Tests run instantly and are deterministic
- Control: You decide when and how time advances
- Isolation: Your code is isolated from system clock changes
- Clarity: Dependencies on time are explicit in your interfaces
Use it!
This pattern is your friend whenever you need to:
- Test code with timeouts or delays
- Verify behavior at specific times or dates
- Make flaky time-dependent tests reliable
- Speed up tests that would otherwise need to wait
This is not a new trick, but it’s very useful and it is under-used, and it can make your life much better when you need to test code that relies on time.
All code in this blog post is CC0, which means you can freely use it however you want.
Happy coding!
Contribute to the discussion by replying on BlueSky!