clockinterface
About 338 words
Enforces using a Clock interface for testable time operations.
Category
Testability
What It Checks
This analyzer detects direct calls to time.Now() that should use an injectable Clock interface. It also flags time.After(), time.Sleep(), time.NewTicker() and time.NewTimer(), however the time package is imported (aliased or dot imports included).
Some code is exempt:
package mainand external test packages (package foo_test), by package name, and_test.gofiles- packages whose import path contains
/cmd/,/cli/,/ui/,/terminal/,/spinner,/prettyor/format - functions named
main,init,New,Format,Print,Printf,PrintlnorString, or starting with one of those words followed by a new camel-case word (NewService,FormatAge, but notNewsletterorinitializeCache) - functions with a parameter whose type mentions
Clock
Why It Matters
Direct time calls are untestable:
func IsExpired(token *Token) bool {
return time.Now().After(token.ExpiresAt) // Can't test!
}With a Clock interface, you can inject test time:
func (s *Service) IsExpired(token *Token) bool {
return s.clock.Now().After(token.ExpiresAt) // Testable!
}Examples
Bad
type Service struct {
db *sql.DB
}
func (s *Service) CreateToken() *Token {
return &Token{
CreatedAt: time.Now(), // Hardcoded time
ExpiresAt: time.Now().Add(24 * time.Hour),
}
}Good
type Clock interface {
Now() time.Time
}
type RealClock struct{}
func (RealClock) Now() time.Time {
return time.Now()
}
type Service struct {
db *sql.DB
clock Clock
}
func (s *Service) CreateToken() *Token {
now := s.clock.Now()
return &Token{
CreatedAt: now,
ExpiresAt: now.Add(24 * time.Hour),
}
}Test with Mock Clock
type MockClock struct {
current time.Time
}
func (m *MockClock) Now() time.Time {
return m.current
}
func (m *MockClock) Advance(d time.Duration) {
m.current = m.current.Add(d)
}
func TestTokenExpiry(t *testing.T) {
clock := &MockClock{current: time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)}
svc := &Service{clock: clock}
token := svc.CreateToken()
// Advance time past expiry
clock.Advance(25 * time.Hour)
if !svc.IsExpired(token) {
t.Error("token should be expired")
}
}Configuration
# .golint-sl.yaml
analyzers:
clockinterface: true # enabled by defaultWhen to Disable
- Simple scripts without tests
- Code where time testing isn't needed
analyzers:
clockinterface: falseRelated Analyzers
- interfaceconsistency - Interface patterns
- mockverify - Mock verification
