How do you make a Go type that calls time.Now() testable, and why is a now func() time.Time field the usual seam?
answer
- a package-level function has no seam
- in Go a function is a value
- the field's type is the signature itself
- time.Now already satisfies it
- the test assigns a closure over time.Date
basics
~20 sStore the clock as a func() time.Time field that defaults to time.Now, and read the time through it. A test assigns a closure returning a fixed time.Date value, so results no longer depend on when the test runs.
solid answer
~50 stime.Now is a package-level function, so there is nothing a test can replace. The fix is to stop calling it directly: give the type a field `now func() time.Time`, read every timestamp through `t.now()`, and default the field to `time.Now` in the constructor. In Go the seam costs almost nothing, because a function is a value and `func() time.Time` is already the whole contract — `time.Now` itself satisfies it, so production wiring is one word and no interface or generated double is needed. A test assigns a closure: `now: func() time.Time { return time.Date(2026, time.March, 8, 23, 59, 0, 0, time.UTC) }`, and assertions on a formatted filename or an expiry decision become exact. Read the clock once per operation and pass the resulting time.Time onward, so two reads inside one unit of work cannot straddle a boundary.
code
go · 11 linestype Reporter struct {
now func() time.Time
}
func NewReporter() *Reporter {
return &Reporter{now: time.Now}
}
func (r *Reporter) filename() string {
return r.now().Format("2006-01-02") + ".log"
}go deeper
Be ready to write the field declaration and the closure a test assigns to it, and to say why time.Now called directly leaves the test nothing to control.
Explain why a function value is enough here when other languages need an interface, and show where the production default gets wired so the type still works when nobody supplies a clock.
Demonstrate reading the clock once per operation and threading the time.Time onward, and be able to argue against a package-level clock variable on parallel-test grounds.
Own whether the clock appears in the exported constructor at all: a required clock parameter is a cost charged to every consumer forever, while an unexported field with a default keeps the seam internal.
## The problem `time.Now` is declared in the standard library as an ordinary package-level function returning a `time.Time`. Go has no way to reassign it, shadow it at run time, or intercept the call. So any function that calls `time.Now()` inside its body has a hidden input that a test cannot supply, and every assertion about the result is really an assertion about the wall clock of whichever machine happens to be running the test. Typical symptoms: a test that asserts a formatted date and passes for 23 hours a day; a test for "the token expires in an hour" that has to sleep; a test that only fails during the first second after midnight, or on the one machine whose clock is a minute fast. ## The seam The Go answer is to make the clock an ordinary dependency of the value that needs it: ```go type Reporter struct { now func() time.Time } func NewReporter() *Reporter { return &Reporter{now: time.Now} } func (r *Reporter) filename() string { return r.now().Format("2006-01-02") + ".log" } ``` Three things are worth noticing. **The field's type is the contract.** `func() time.Time` says everything: no arguments, one instant out. There is no interface to declare, no name to bikeshed, and nothing to keep in sync with an implementation. **`time.Now` already satisfies it.** Functions are first-class values in Go, and a func value's type is its signature, so `now: time.Now` compiles with no adapter. That is why a one-method dependency in Go is usually written as a function value rather than as an interface with one method plus a struct that implements it. **The test supplies a closure.** `func() time.Time { return fixed }` is the whole double. It has no expectations to configure, no ordering to satisfy, and it reads in the test source as data. ## Wiring the default Two defensible styles. Set it in the constructor (`&Reporter{now: time.Now}`), which is explicit and keeps the call site free of checks. Or leave the field nil and fall back at the read site: ```go func (r *Reporter) clock() time.Time { if r.now == nil { return time.Now() } return r.now() } ``` The second keeps the zero value of the struct usable, which matters for a type callers may construct with a plain literal. What you should not do is spread `if r.now == nil` through a dozen methods; funnel it through one place. ## Reading the clock once A subtle bug the seam does not fix by itself: calling `r.now()` several times inside one operation. Even with a real clock those reads return different instants, so a record can be stamped with a start on one side of midnight and an end on the other, and a comparison can decide that something created "now" is already expired. Read once at the top of the operation and pass the `time.Time` value down as a parameter. Time values are small and immutable-by-copy, so passing them is cheap and makes the dependency visible in the signatures of the functions that actually use it. ## What the fake must be able to do A fake that returns one frozen instant forever is perfect for formatting and for "what does this record look like", and useless for anything that waits for time to pass: code that loops until the deadline is reached will never terminate, and any measured duration is zero. When the behaviour under test is about progression, give the fake state: ```go type fakeClock struct{ t time.Time } func (c *fakeClock) Now() time.Time { return c.t } func (c *fakeClock) advance(d time.Duration) { c.t = c.t.Add(d) } ``` and inject `c.Now` as the `func() time.Time`. The method value `c.Now` is itself a `func() time.Time`, so the field type does not change. ## What not to do Do not hide the clock in a package-level variable (`var now = time.Now`) that tests reassign. It compiles, and it is a shared mutable global: two tests running with `t.Parallel()` will fight over it, and the reassignment leaks into every other test in the package until something puts it back. Keep the clock on the value that uses it. Do not sleep in the test to move real time forward. That trades a fast deterministic test for a slow one that is still nondeterministic under load. And do not reach for a mock-generation tool for a dependency whose entire surface is one niladic function. The closure is shorter than the annotation that would generate the mock.
- Should the default time.Now be wired in the constructor or checked for nil at the read site?Either works. Setting it in the constructor is explicit and keeps method bodies clean; a nil check keeps the struct's zero value usable for callers who build it with a plain literal. What matters is that the fallback lives in exactly one place rather than being repeated in every method that needs a timestamp.
- Why read the injected clock once per operation instead of at every point a timestamp is needed?Successive reads return different instants, even from a real clock. A record can then get a start before midnight and an end after it, or an object can look expired at creation. Read once at the entry point and pass the resulting time.Time down as a parameter, which also makes the dependency visible in the inner functions' signatures.
- What breaks if the fake clock returns the same instant on every call?Anything that depends on time moving. A loop that waits for a deadline never finishes, a measured elapsed duration is always zero, and a rate limiter never refills. For those tests give the fake state — a struct holding a time.Time with an advance method — and inject its Now method value, which is still a func() time.Time.
- Why not put the clock in a package-level variable that tests reassign?It is shared mutable global state. Tests marked t.Parallel() overwrite each other's value, a test that forgets to restore it corrupts every later test in the package, and the dependency is invisible in the type's API. Keeping the field on the value that reads the clock scopes the substitution to one instance.
Instead of letting the machine shout the time through the window, the type is handed a small dial it reads. Production sets the dial to follow the wall clock; the test turns it to a chosen minute and leaves it there.
saying these in an interview costs you the question
- Claims time.Now can be monkey-patched at run time in Go
- Reaches for a generated double for a one-function dependency
- Sleeps in the test to reach the next second or day
- Reads the injected clock several times inside one operation
- Stores the clock in a package-level var that parallel tests share
- Says the fake must always return one frozen instant