liveBook · Manning

Dependency injection

As explained in 100 Go Mistakes and How to Avoid Them

Dependency injection is a technique in which a component receives required dependencies from its caller rather than constructing or retrieving them internally.

A common testing mistake is reading the current time directly inside production code. Injecting a time function lets tests replace an unpredictable dependency with a deterministic implementation. section 11.6

Injecting a clock into Cache

type now func() time.Time type Cache struct { events []Event now now } func NewCache() *Cache { return &Cache{now: time.Now} } A test can assign a function returning a fixed timestamp, making TrimOlderThan reproducible. A mutable global such as var now = time.Now shares state across tests, prevents safe parallel execution, and weakens isolation. section 11.6

Choose the injection shape according to the dependency and API boundary:

  • Keep time operations in injectable struct dependencies; add another function or group related operations behind an interface when needed. section 11.6
  • Pass the current time, or a calculated cutoff time.Time, from the caller when exposing an unexported dependency is inconvenient; this avoids extra types and stubs. section 11.6
type Cache struct {
    mu     sync.RWMutex
    events []Event
}
 
type Event struct {
    Timestamp time.Time
    Data string
}
 
func (c *Cache) TrimOlderThan(since time.Duration) {
    c.mu.RLock()
    defer c.mu.RUnlock()
 
    t := time.Now().Add(-since)               #1
    for i := 0; i < len(c.events); i++ {
        if c.events[i].Timestamp.After(t) {
            c.events = c.events[i:]           #2
            return
        }
    }
}

Compare the direct time.Now call with the Cache operation that tests must make deterministic. section 11.6

How 100 Go Mistakes and How to Avoid Them covers it

How deep it goes
Explains struct fields, constructors, fake functions, interfaces, globals, and caller-provided values, but stops short of broader dependency graphs.
Examples
Uses a Cache, event timestamps, time.Now, fixed timestamps, fake clock functions, and cutoff-time arguments.
What it assumes
Assumes familiarity with Go structs, functions, methods, interfaces, constructors, and unit tests.
What you can do after
Diagnose flaky time-dependent tests and choose struct injection or caller-provided time to make outcomes reproducible.
How it connects
Connects dependency injection with unit testing, test isolation, mutable shared state, interfaces, and flaky tests.

Also explained in

Compare how 21 books explain dependency injection →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of 100 Go Mistakes and How to Avoid Them. The section links point to the parts of the book it draws on.