Time in Golang
Clock Dependency
time.Now()
can cause a lot of side effects in a codebase. One example is that you can't test the "current" time that happened in a function you called in the past.
For example, let's say we have the following:
package mypackage
import "time"
func MyTimeFunc() time.Time {
return time.Now()
}
func TestMyTimeFunc(t *testing.T) {
if MyTimeFunc() != time.Now() {
// This will error!
// The time in the function and the test happen at different times
t.Errorf("time was not now")
}
}
How do we test the contents of the return here? If we want to assert the time we need a way to know what time.Now()
was when the function was called.
Instead of directly using the time
package, we can pass a clock as a
dependency and call .Now()
on that. Then in our tests, we can assert against
that clock! The clock can be anything as long as it adheres to the clock.Clock
interface as defined in the facebookgo/clock
package. We could, for
example, make the clock always return the year 0, or the 2019 New Year, or maybe
your birthday! In this clock package, there are two clocks.
- The real clock where
clock.Now()
will calltime.Now()
. - A mock clock where
clock.Now()
always returns epoch time. We'll show later how to change that!
Let's look at the example above with the clock
package.
package mypackage
import "fmt"
import "time"
import "github.com/facebookgo/clock"
func MyTimeFunc(clock clock.Clock) time.Time {
return clock.Now()
}
// Then our caller
func main() {
// clock.New() creates a clock that uses the time package
// it will output current time when .Now() is called
fmt.Print(MyTimeFunc(clock.New()))
}
Then in our tests we can use a mock clock that freezes .Now()
at epoch time:
func TestMyTimeFunc(t *testing.T) {
testClock := clock.NewMock()
if MyTimeFunc(testClock) != testClock.Now() {
// both should equal epoch time, we won't hit this error
t.Errorf("time was not now")
}
}
Cool, but what if I want to use a different date?
Say my test relies on our TestYear
constant. The clock.Mock clock allows us to add durations to the clock and set the current time. Note that the clock.Clock
interface does not allow this, it needs to happen before passing the mock clock through the interface parameter.
Setting the Mock Clock
Here's an example using the test above and setting the time to September 30 of TestYear:
func TestMyTimeFunc(t *testing.T) {
testClock := clock.NewMock()
dateToTest := time.Date(TestYear, time.September, 30, 0, 0, 0, 0, time.UTC)
timeDiff := dateToTest.Sub(c.Now())
testClock.Add(timeDiff)
if MyTimeFunc(testClock) != testClock.Now() {
// both will now be September 30 of TestYear
// we'll pass the test again
t.Errorf("time was not now")
}
}
MilMove Calendar Utilities
The MilMove project has a set of date/calendar utility to help develop and test. You can find them in the dates package
For testing, we also have TestYear
in the constants package, which should be used instead of the current year.