Tock

a mock clock library for golang
Usage
First, wherever you use timer functions, use them through the tock interface instead:
import (
"time"
)
func mySleeper() bool {
time.Sleep(1 * time.Second)
return true
}
replace with:
import (
"github.com/aspenmesh/tock"
)
var clock := tock.NewReal()
func mySleeper() bool {
clock.Sleep(1 * time.Second)
return true
}
This causes no behavior change. tock.NewReal() is a real clock - it passes
through clock.Sleep to time.Sleep and so on.
Next, in your unit tests, don't use tock.NewReal(), instead use tock.NewMock().
For example, in mySleepers.go:
import (
"github.com/aspenmesh/tock"
)
func mySleeper(clock tock.Clock) {
clock.Sleep(1 * time.Second)
return true
}
In the real implementation, call like:
complete := mySleeper(tock.NewReal())
In the unit test, you can do:
func TestMySleeper(t *testing.T) {
c := tock.NewMock(tock.MockOptions{})
go func() {
c.BlockUntil(1)
c.Advance(1 * time.Second)
}()
complete := mySleeper(c)
if ! complete {
t.Errorf("Complete wasn't true")
}
}
Why?
Using real wall time in unit tests is risky. Fundamentally, a timer is
guaranteed to fire no-earlier-than the time you ask for, but there is no
guarantee that it won't fire later. Potentially quite a bit later, especially
when you are running unit tests on a busy CI system. This problem seems to
exaggerate right before a release for some reason :-) .
Also, you want to keep timers short when you are unit testing so that the unit
tests run fast, but this increases the risk that timers don't fire exactly when
you are expecting them to. It's also sub-ideal to use different timer values
in unit tests than in the real implementation because you have to remember to
update the "mock" values and convert back and forth between the mock timebase
and the real timebase when debugging.
Instead you can use a mock clock library which lets you implement your own fake
wall clock that you can advance under test conditions. You get fast unit
tests, and guarantees that timers fire exactly when you expect.
Why this library?
There were a few existing mock clock libraries for golang. There were a couple
of things that were important to us:
-
The channel that timers and tickers wait on is named C, directly in the
struct, not accessible via interface (like C() or Chan()). This matches
the real time library. This reduces the amount of code you have to change to
use tock.
-
Tickers are re-queued directly in the Advance() thread, so if you register a
ticker for every 500 Milliseconds, it is guaranteed to fire at 0.5, 1.0, 1.5,
2.0 seconds of fake wall time. An alternative implementation that didn't work
for me uses a gofunc to re-register the ticker so it races to re-register and
the firing may not be regular. (In reality, you shouldn't count on Tickers
firing at exactly 0.5, 1.0, etc but some unit tests do)
-
It is safe for a timer callback to re-register another timer.
-
If you set the Yield option, Timer notification yields using
runtime.Gosched() and a short time.Sleep() to maximize the likelihood that
downstream gofuncs, notifications, etc happen before moving on. The most
defensively written tests won't require this option - if you need to use it, it
suggests you have a dependency on a timer callback function completing before
some other action happens.
What's better?
You may be better off minimizing bare usage of timers at all, and instead try
to use channels whenever possible. You can use timers to publish to those
channels or cancel contexts but try to constrain the timers to a small area.
This can make it much easier to write robust and fast unit tests.
Inspired by
This was inspired by jonboulle's great
clockwork library.