timex
is a test-friendly replacement for the time
package.
Just replace your time.Now()
by a timex.Now()
call, etc.
Use timex.Override(...)
to replace the current implementation by another one, and use the function it returns to restore the default implementation. You can't override from several tests at the same time. You can use an auto-generated by mockery
mock from timexmock
package, or a controlled implementation from timextest
.
There's a timexmock.Mocked(func(mocked *timexmock.Implementation) { ... })
wrapper that automatically creates a mock, sets it as the implementation to be used and defers a tear down to set the default implementation again.
Example:
func TestSleep(t *testing.T) {
timexmock.Mocked(func(mocked *timexmock.Implementation) {
mocked.On("Sleep", someDuration).Once()
defer mocked.AssertExpectations(t)
timex.Sleep(someDuration)
})
}
Timextest provides a more complex API useful to control the behavior of concurrent programs, it is especially useful when the code interacts with timers like time.Ticker
. Just like timexmock
, timextest
also provides a timextest.Mocked(time.Time, func(*TestImplementation))
function to make mocking easier. Few examples can be found in timextest/example_test.go
, this is one of them:
func ExampleTestImplementation_NewTicker() {
timextest.Mocked(now, func(mockedtimex *timextest.TestImplementation) {
go func() {
ticker := timex.NewTicker(time.Hour)
for t := range ticker.C() {
fmt.Printf("%s\n", t)
}
}()
tickerCall := <-mockedtimex.NewTickerCalls
tickerCall.Mock.Tick(now.Add(time.Second))
tickerCall.Mock.Tick(now.Add(2 * time.Second))
// Output:
// 2009-11-10 23:00:01 +0000 UTC
// 2009-11-10 23:00:02 +0000 UTC
})
}
There's an obvious performance impact caused by the indirection of the call, it's actually 20-30% slower, however, in absolute numbers we're talking about 30 nanoseconds per call, so you probably should not worry about that. Notice that the difference is so small that it's not easy to get a stable result.
$ go test -run=NONE -benchmem -benchtime=5s -bench=. .
goos: darwin
goarch: amd64
pkg: github.com/cabify/timex
BenchmarkTimeNow-4 49619665 112 ns/op 0 B/op 0 allocs/op
BenchmarkTimexNow-4 41256012 145 ns/op 0 B/op 0 allocs/op
If you're really worried about performance, you can disable part of the indirection by compiling with timex_disable
tag, which will provide results similiar to the native implemenation calls:
$ go test -run=NONE -benchmem -benchtime=5s -bench=. -tags=timex_disable .
goos: darwin
goarch: amd64
pkg: github.com/cabify/timex
BenchmarkTimeNow-4 49866967 116 ns/op 0 B/op 0 allocs/op
BenchmarkTimexNow-4 47965780 109 ns/op 0 B/op 0 allocs/op
Oh... yes, we're changing global variables and we'll obviously burn in hell, but if you're really into DI, you can also accept timex.Implementation
interface as a dependency, and then inject either timex.Default{}
or a testable implementation.