evals/evals.json
[
{
"id": 1,
"name": "build-constraint-on-injector",
"description": "Tests that the model adds //go:build wireinject to injector files to prevent duplicate-symbol compile errors",
"prompt": "I'm setting up google/wire in my Go project. Here's my injector file:\n\n```go\npackage main\n\nimport \"github.com/google/wire\"\n\nfunc InitApp() (*App, error) {\n wire.Build(InfraSet, ServiceSet, NewApp)\n return nil, nil\n}\n```\n\nWire generates wire_gen.go successfully, but when I run `go build`, I get a 'redeclared in this block' compile error for InitApp. What's wrong and how do I fix it?",
"trap": "Without the skill, the model may suggest renaming the function, reorganizing packages, or not identify that the missing //go:build wireinject tag is the cause — both the stub and wire_gen.go define InitApp, causing the duplicate.",
"assertions": [
{"id": "1.1", "text": "Identifies the missing //go:build wireinject build tag as the root cause"},
{"id": "1.2", "text": "Shows //go:build wireinject as the first line of the injector file"},
{"id": "1.3", "text": "Explains that the tag prevents the stub from being compiled into the binary (only wire_gen.go compiles)"},
{"id": "1.4", "text": "Does NOT suggest renaming the function or reorganizing packages as the fix"},
{"id": "1.5", "text": "Does NOT suggest deleting wire_gen.go as the fix"}
]
},
{
"id": 2,
"name": "interface-binding-required",
"description": "Tests that wire.Bind is required for interface-to-concrete mappings and cannot be inferred",
"prompt": "I have this Go code using google/wire:\n\n```go\n// repo.go\ntype UserStore interface {\n GetUser(id int64) (*User, error)\n}\n\ntype PostgresUserRepo struct{ db *sql.DB }\nfunc (r *PostgresUserRepo) GetUser(id int64) (*User, error) { ... }\nfunc NewUserRepo(db *sql.DB) *PostgresUserRepo { return &PostgresUserRepo{db: db} }\n\n// service.go\nfunc NewUserService(store UserStore) *UserService { return &UserService{store: store} }\n\n// wire_providers.go\nvar AppSet = wire.NewSet(NewDB, NewUserRepo, NewUserService)\n```\n\nWhen I run `wire ./...` I get: `no provider found for UserStore`. NewUserRepo returns *PostgresUserRepo which clearly implements UserStore. Why doesn't wire figure this out?",
"trap": "Without the skill, the model might suggest wire should automatically resolve the interface, or suggest wrapping NewUserRepo to return UserStore directly, missing the explicit wire.Bind requirement.",
"assertions": [
{"id": "2.1", "text": "Explains that wire never auto-resolves interface satisfaction — bindings must be explicit"},
{"id": "2.2", "text": "Shows wire.Bind(new(UserStore), new(*PostgresUserRepo)) added to the provider set"},
{"id": "2.3", "text": "Places wire.Bind inside the same wire.NewSet (or adds it to a set in wire.Build)"},
{"id": "2.4", "text": "Explains WHY wire requires explicit bindings (predictability — avoids surprise rebinding when new implementations are added)"},
{"id": "2.5", "text": "Does NOT suggest changing NewUserRepo to return UserStore directly as the primary fix"}
]
},
{
"id": 3,
"name": "duplicate-type-named-wrapper",
"description": "Tests the named-type pattern to disambiguate multiple values of the same underlying type",
"prompt": "I'm building a Go service with google/wire. I need to inject two database connection strings — one for the primary database and one for a read replica. I tried this:\n\n```go\nfunc NewPrimaryDSN() string { return os.Getenv(\"PRIMARY_DSN\") }\nfunc NewReplicaDSN() string { return os.Getenv(\"REPLICA_DSN\") }\n\nvar DBSet = wire.NewSet(NewPrimaryDSN, NewReplicaDSN, NewPrimaryDB, NewReplicaDB)\n```\n\nWire complains about multiple bindings for string. How should I structure this?",
"trap": "Without the skill, the model might suggest using wire.Value or provider arguments, or use a config struct — missing the idiomatic named-type wrapper pattern that wire's own docs recommend.",
"assertions": [
{"id": "3.1", "text": "Introduces distinct named types (e.g., type PrimaryDSN string and type ReplicaDSN string)"},
{"id": "3.2", "text": "Updates NewPrimaryDSN to return PrimaryDSN and NewReplicaDSN to return ReplicaDSN"},
{"id": "3.3", "text": "Updates NewPrimaryDB and NewReplicaDB signatures to accept the named types"},
{"id": "3.4", "text": "Explains that wire enforces one provider per type, so distinct named types are the correct solution"},
{"id": "3.5", "text": "Does NOT suggest using a single Config struct with both DSNs as the primary fix (that avoids the problem rather than solving it with named types)"}
]
},
{
"id": 4,
"name": "cleanup-signature",
"description": "Tests the (T, func(), error) cleanup provider pattern instead of manual defer in main",
"prompt": "I'm using google/wire to wire my Go service. I need my *sql.DB connection pool to be closed when the app shuts down. Currently I'm doing this in main:\n\n```go\nfunc main() {\n app, err := InitApp()\n if err != nil { log.Fatal(err) }\n defer db.Close() // but I don't have access to db here!\n app.Run()\n}\n```\n\nI realize I need the DB closed on shutdown, but InitApp() only returns *App. How should I wire cleanup with google/wire?",
"trap": "Without the skill, the model might suggest passing db out of InitApp as a second return value, or storing it as a global, missing the (T, func(), error) cleanup provider pattern.",
"assertions": [
{"id": "4.1", "text": "Changes NewDB to return (*sql.DB, func(), error) where the cleanup function calls db.Close()"},
{"id": "4.2", "text": "Changes the injector function to return (*App, func(), error) to propagate the cleanup chain"},
{"id": "4.3", "text": "Shows main calling defer cleanup() after the nil-check"},
{"id": "4.4", "text": "Explains that wire chains cleanup functions and calls them in reverse construction order"},
{"id": "4.5", "text": "Does NOT suggest passing db as an extra return value from InitApp alongside *App"}
]
},
{
"id": 5,
"name": "no-edit-wire-gen",
"description": "Tests that the model never edits wire_gen.go and instructs re-running wire ./... instead",
"prompt": "I added a new *Logger parameter to my NewServer constructor in my google/wire project:\n\n```go\nfunc NewServer(db *sql.DB, log *zap.Logger) *Server { ... }\n```\n\nNow `go build` fails with 'too few arguments in call to NewServer'. The error is inside wire_gen.go on line 47. Should I edit wire_gen.go to add the logger argument there, or is there another way?",
"trap": "Without the skill, a model may suggest editing wire_gen.go directly to 'fix' the build error quickly, which would be overwritten on the next wire run.",
"assertions": [
{"id": "5.1", "text": "Explicitly says NOT to edit wire_gen.go (it is always overwritten)"},
{"id": "5.2", "text": "Instructs running wire ./... to regenerate wire_gen.go"},
{"id": "5.3", "text": "Explains that *zap.Logger must be provided in the graph (either via a provider or wire.Value)"},
{"id": "5.4", "text": "Shows how to add NewLogger (or wire.Value) to the appropriate wire.NewSet so the dependency is satisfied"},
{"id": "5.5", "text": "Does NOT present editing wire_gen.go as an option"}
]
},
{
"id": 6,
"name": "provider-set-organization",
"description": "Tests per-package provider set organization instead of one giant set in main",
"prompt": "My Go service using google/wire is growing. I currently have everything in one place:\n\n```go\n// wire.go\n//go:build wireinject\n\nfunc InitApp() (*App, func(), error) {\n wire.Build(\n NewConfig, NewDB, NewCache, NewLogger,\n NewUserRepo, NewOrderRepo, NewProductRepo,\n wire.Bind(new(UserStore), new(*PostgresUserRepo)),\n wire.Bind(new(OrderStore), new(*PostgresOrderRepo)),\n wire.Bind(new(ProductStore), new(*PostgresProductRepo)),\n NewUserService, NewOrderService, NewProductService,\n NewHTTPServer, NewRouter,\n NewApp,\n )\n return nil, nil, nil\n}\n```\n\nThis is getting unwieldy. How should I organize this with google/wire?",
"trap": "Without the skill, the model may just split the providers into helper variables in the same package, missing the idiomatic per-package wire.NewSet pattern.",
"assertions": [
{"id": "6.1", "text": "Introduces per-package wire.NewSet variables (e.g., InfraSet, RepoSet, ServiceSet, TransportSet)"},
{"id": "6.2", "text": "Each set lives in its own package's wire.go file (not all in main)"},
{"id": "6.3", "text": "The injector wire.Build references the set variables rather than individual providers"},
{"id": "6.4", "text": "wire.Bind declarations move into the relevant package's set (not into wire.Build directly)"},
{"id": "6.5", "text": "Explains the benefit: per-package sets are independently composable and keep the injector readable"}
]
},
{
"id": 7,
"name": "injector-parameter-vs-value-provider",
"description": "Tests using wire.Value or injector parameters for pre-built values instead of wrapper constructors",
"prompt": "In my Go app using google/wire, I parse a *Config struct from command-line flags in main() before calling InitApp. I tried writing a no-op provider:\n\n```go\nvar parsedCfg *Config\n\nfunc ProvideConfig() *Config { return parsedCfg }\n\nvar AppSet = wire.NewSet(ProvideConfig, ...)\n```\n\nThis works but feels wrong — I'm using a global variable. Is there a cleaner way to pass a pre-built *Config into the wire graph?",
"trap": "Without the skill, the model might suggest keeping the global variable pattern or using init(), missing both wire.Value and the injector-parameter patterns.",
"assertions": [
{"id": "7.1", "text": "Shows the injector-parameter approach: func InitApp(cfg *Config) (*App, func(), error) with wire.Build"},
{"id": "7.2", "text": "OR shows wire.Value(cfg) inside wire.Build — both are valid answers"},
{"id": "7.3", "text": "Explains that injector parameters are treated as pre-built providers by wire"},
{"id": "7.4", "text": "Does NOT use a global variable as the recommended solution"},
{"id": "7.5", "text": "Does NOT suggest using init() to set the value"}
]
},
{
"id": 8,
"name": "fields-of-struct",
"description": "Tests wire.FieldsOf to expose struct fields as individual graph nodes",
"prompt": "I have a single Config struct in my Go app with google/wire:\n\n```go\ntype Config struct {\n DatabaseDSN string\n CacheAddress string\n APIKey string\n}\n\nfunc NewConfig() *Config { return loadFromEnv() }\n```\n\nNewDB needs a DatabaseDSN string, NewCache needs a CacheAddress string, NewExternalClient needs an APIKey string — but all three are plain strings. How do I make these available to the wire graph without creating three separate provider functions?",
"trap": "Without the skill, the model will suggest three named-type wrappers or three extraction functions, missing wire.FieldsOf which promotes struct fields directly.",
"assertions": [
{"id": "8.1", "text": "Uses wire.FieldsOf(new(Config), \"DatabaseDSN\", \"CacheAddress\", \"APIKey\") or a subset"},
{"id": "8.2", "text": "Places wire.FieldsOf inside the provider set or wire.Build"},
{"id": "8.3", "text": "Updates NewDB, NewCache, NewExternalClient to accept the string fields as parameters (or uses named types alongside FieldsOf)"},
{"id": "8.4", "text": "Explains that wire.FieldsOf promotes struct fields as individual graph nodes without manual extraction functions"},
{"id": "8.5", "text": "Does NOT suggest writing three separate func GetDatabaseDSN(c *Config) string extractor functions as the primary recommendation"}
]
},
{
"id": 9,
"name": "test-injector-pattern",
"description": "Tests the test-injector pattern with wire.Bind for fake dependencies instead of runtime mocking hacks",
"prompt": "My Go service is wired with google/wire. I have a Mailer interface implemented by SMTPMailer in production. I want integration tests that use a FakeMailer instead — recording sent emails — without modifying the production provider sets. The test must wire the full graph (not just NewUserService in isolation). How should I approach this?",
"trap": "Without the skill, the model may suggest monkey-patching, a global variable for the mailer, or a runtime DI container for tests — missing the test-injector pattern with a test-only wire.NewSet and wire.Bind.",
"assertions": [
{"id": "9.1", "text": "Creates a test-only provider set (e.g., TestMailerSet) with NewFakeMailer and wire.Bind(new(Mailer), new(*FakeMailer))"},
{"id": "9.2", "text": "Creates a test injector function in a _test.go file with //go:build wireinject"},
{"id": "9.3", "text": "The test injector's wire.Build composes the production sets with the test-only set"},
{"id": "9.4", "text": "Does NOT suggest global variables, monkey-patching, or a runtime DI container for tests"},
{"id": "9.5", "text": "Mentions that wire ./... (or go generate) must be run to produce the test-injector generated code"}
]
},
{
"id": 10,
"name": "wire-vs-fx-for-daemon",
"description": "Tests that the model recommends uber-go/fx over wire for long-running services that need lifecycle management",
"prompt": "I'm starting a new Go HTTP server project and evaluating DI options. A colleague suggested google/wire because 'it's simpler and type-safe at compile time.' The server needs graceful shutdown (drain in-flight requests), OnStart/OnStop hooks for the database pool and metrics exporter, and should handle SIGINT/SIGTERM. Should I use wire?",
"trap": "Without the skill, the model may agree that wire is suitable because it's simple and compile-time safe, not recognizing that lifecycle, signal handling, and hook ordering are exactly what fx provides and wire explicitly lacks.",
"assertions": [
{"id": "10.1", "text": "Identifies that wire has no built-in lifecycle management (no OnStart/OnStop hooks)"},
{"id": "10.2", "text": "Identifies that wire has no built-in signal handling (SIGINT/SIGTERM)"},
{"id": "10.3", "text": "Recommends uber-go/fx (or at minimum flags it as the better fit) for a long-running HTTP daemon with lifecycle needs"},
{"id": "10.4", "text": "Does NOT recommend wire as sufficient for a service requiring graceful shutdown and lifecycle hooks"},
{"id": "10.5", "text": "Mentions that with wire the developer must implement shutdown and signal handling manually"}
]
}
]
references/advanced.md
# Advanced — google/wire
Detail topics referenced from `SKILL.md`. Each section is self-contained.
## Cleanup Chains
When a provider returns `(T, func(), error)`, Wire adds the cleanup to a chain. The generated injector runs cleanups in **reverse construction order**: the last-built dependant is cleaned up first, ensuring dependants are torn down before their dependencies.
```go
// Provider with cleanup
func NewDB(cfg *Config) (*sql.DB, func(), error) {
db, err := sql.Open("postgres", string(cfg.DSN))
if err != nil { return nil, nil, err }
return db, func() { db.Close() }, nil
}
func NewCache(cfg *Config) (*redis.Client, func(), error) {
c := redis.NewClient(&redis.Options{Addr: cfg.CacheAddr})
return c, func() { c.Close() }, nil
}
```
Wire generates something like:
```go
func InitApp() (*App, func(), error) {
cfg := NewConfig()
db, dbCleanup, err := NewDB(cfg)
if err != nil { return nil, nil, err }
cache, cacheCleanup, err := NewCache(cfg)
if err != nil {
dbCleanup() // already-built cleanups run on partial failure
return nil, nil, err
}
app := NewApp(db, cache)
return app, func() {
cacheCleanup() // reverse order
dbCleanup()
}, nil
}
```
**Caller pattern** — guard against nil cleanup on construction failure:
```go
app, cleanup, err := InitApp()
if err != nil { log.Fatal(err) }
defer cleanup()
```
Wire always returns a non-nil cleanup function when construction succeeds. If construction fails midway, the returned `cleanup` is nil — guard before calling.
## Multiple Injectors in One Package
A package can contain multiple injector functions. Each must live in a file with `//go:build wireinject`. All generated functions land in `wire_gen.go` in the same package.
```go
//go:build wireinject
package main
// Production injector
func InitProdApp() (*App, func(), error) {
wire.Build(ProdSet, NewApp)
return nil, nil, nil
}
// Development injector with debug providers
func InitDevApp() (*App, func(), error) {
wire.Build(DevSet, NewApp)
return nil, nil, nil
}
```
Select at runtime with a flag, or at build time with separate `//go:build prod` / `//go:build !prod` constraints on the injector files.
## wire.NewSet Nesting Strategies
Sets can contain other sets, building a hierarchy that mirrors your package structure.
```go
// pkg/config/wire.go
var ConfigSet = wire.NewSet(NewConfig)
// pkg/infra/wire.go
var InfraSet = wire.NewSet(
config.ConfigSet, // embed upstream set
NewDB,
NewCache,
)
// pkg/service/wire.go
var ServiceSet = wire.NewSet(
NewUserService,
wire.Bind(new(UserStore), new(*UserRepo)),
)
// wire.go (injector)
wire.Build(infra.InfraSet, service.ServiceSet, NewApp)
```
**Library set stability rules** (from upstream best practices):
- Safe: replace one provider with another that has the same or fewer inputs, in the same release.
- Safe: introduce a brand-new output type not previously provided.
- Breaking: add a new required input to a provider — downstream injectors cannot satisfy it.
- Breaking: remove a provided output type — downstream injectors that depend on it fail.
- Breaking: add a type that the injector already provides — Wire reports a duplicate.
## `wire:"-"` Exclusion Tag
Exclude a struct field from `wire.Struct` injection by tagging it:
```go
type Server struct {
Logger *zap.Logger
DB *sql.DB
mu sync.Mutex `wire:"-"` // unexported — auto-excluded
Timeout time.Duration `wire:"-"` // exported but opt-out
}
wire.Struct(new(Server), "*") // injects Logger and DB; skips mu and Timeout
```
Unexported fields are always skipped regardless of the tag.
## Common Codegen Errors
| Error message | Root cause | Fix |
| --- | --- | --- |
| `no provider found for TYPE` | A dependency is not provided by any set in `wire.Build` | Add the missing provider or set |
| `multiple bindings for TYPE` | Two providers return the same type | Use named types or remove the duplicate |
| `argument N has no provider for TYPE` | An interface is requested but no `wire.Bind` maps to it | Add `wire.Bind(new(Iface), new(*Impl))` to a set |
| `cycle detected` | A → B → A circular dependency | Break the cycle by introducing an interface or factory |
| `wire.Build used outside of injector function` | `wire.Build` called from a non-injector function | Only call `wire.Build` inside functions with the build tag |
| duplicate symbol / redeclared in this block | Injector file is missing `//go:build wireinject` | Add the build tag as the first line |
## Codegen Flags
```bash
# Specify output file prefix (default: wire_gen)
wire -output_file_prefix=init gen ./cmd/server
# Apply build tags during generation
wire -tags=integration gen ./...
# Prepend a header file (e.g., license comment) to generated output
wire -header_file=hack/boilerplate.go.txt gen ./...
```
## `panic(wire.Build(...))` Alternate Syntax
Wire accepts either a dummy return or a `panic` call as the injector body. The `panic` form avoids writing zero-value returns for complex types:
```go
// Preferred when return types are complex or error-prone to zero-initialize
func InitApp(ctx context.Context) (*App, func(), error) {
panic(wire.Build(AppSet))
}
```
Wire detects both forms and replaces the body during codegen. The `panic` is never reached in the compiled binary — only the generated `wire_gen.go` version is compiled.
## Accepting External Values as Injector Arguments
When a value is constructed before wire runs (e.g., parsed flags, an `http.Client` from a test), pass it as a parameter to the injector rather than providing it from within the graph:
```go
//go:build wireinject
func InitApp(cfg *Config) (*App, func(), error) {
wire.Build(InfraSet, ServiceSet, NewApp)
return nil, nil, nil
}
// main.go
cfg := parseFlags()
app, cleanup, err := InitApp(cfg)
```
Wire treats injector parameters as pre-built providers — they satisfy dependencies without needing a `wire.NewSet` entry.
## Quick Reference
| Symbol | Purpose |
| --- | --- |
| `wire.NewSet(providers...)` | Group providers into a reusable set |
| `wire.Build(sets...)` | Declare injector body (codegen replaces it) |
| `wire.Bind(new(Iface), new(*Concrete))` | Bind interface to concrete type |
| `wire.Struct(new(T), "Field", ...)` | Inject struct fields from the graph |
| `wire.Struct(new(T), "*")` | Inject all non-excluded fields |
| `wire.Value(expr)` | Bind a constant expression (no fn calls/channels) |
| `wire.InterfaceValue(new(I), value)` | Bind a value to an interface type |
| `wire.FieldsOf(new(T), "Field", ...)` | Promote struct fields as individual graph nodes |
| `//go:build wireinject` | Build tag: exclude injector stub from binary |
| `wire_gen.go` | Generated output — commit, never edit |
| `wire ./...` | Regenerate all injectors in the module |
| `wire check ./...` | Validate graph without regenerating |
references/recipes.md
# Recipes — google/wire
End-to-end examples. Each recipe is self-contained.
## HTTP Server with Postgres and Redis
A typical service: parsed config → DB (with cleanup) → Redis (with cleanup) → repo → service → HTTP server.
```
myapp/
├── config/
│ ├── config.go
│ └── wire.go
├── infra/
│ ├── db.go
│ ├── cache.go
│ └── wire.go
├── repo/
│ ├── user.go
│ └── wire.go
├── service/
│ ├── user.go
│ └── wire.go
├── transport/
│ ├── handler.go
│ └── wire.go
├── wire.go // injector — //go:build wireinject
├── wire_gen.go // generated — commit this
└── main.go
```
```go
// config/config.go
type Config struct {
Addr string
DSN string
CacheAddr string
}
func NewConfig() *Config {
return &Config{
Addr: env("ADDR", ":8080"),
DSN: mustEnv("DATABASE_URL"),
CacheAddr: env("REDIS_ADDR", "localhost:6379"),
}
}
// config/wire.go
var ConfigSet = wire.NewSet(NewConfig)
```
```go
// infra/db.go
func NewDB(cfg *config.Config) (*sql.DB, func(), error) {
db, err := sql.Open("postgres", cfg.DSN)
if err != nil { return nil, nil, err }
if err := db.Ping(); err != nil { db.Close(); return nil, nil, err }
return db, func() { db.Close() }, nil
}
// infra/cache.go
func NewRedis(cfg *config.Config) (*redis.Client, func(), error) {
c := redis.NewClient(&redis.Options{Addr: cfg.CacheAddr})
if err := c.Ping(context.Background()).Err(); err != nil {
return nil, nil, err
}
return c, func() { c.Close() }, nil
}
// infra/wire.go
var InfraSet = wire.NewSet(NewDB, NewRedis)
```
```go
// repo/user.go
type UserStore interface {
GetUser(ctx context.Context, id int64) (*User, error)
}
type PostgresUserRepo struct{ db *sql.DB }
func NewUserRepo(db *sql.DB) *PostgresUserRepo { return &PostgresUserRepo{db: db} }
// repo/wire.go
var RepoSet = wire.NewSet(
NewUserRepo,
wire.Bind(new(UserStore), new(*PostgresUserRepo)),
)
```
```go
// service/user.go
type UserService struct {
store repo.UserStore
cache *redis.Client
}
func NewUserService(store repo.UserStore, cache *redis.Client) *UserService {
return &UserService{store: store, cache: cache}
}
// service/wire.go
var ServiceSet = wire.NewSet(NewUserService)
```
```go
// wire.go
//go:build wireinject
package main
func InitApp() (*transport.Handler, func(), error) {
wire.Build(
config.ConfigSet,
infra.InfraSet,
repo.RepoSet,
service.ServiceSet,
transport.NewHandler,
)
return nil, nil, nil
}
```
```go
// main.go
func main() {
handler, cleanup, err := InitApp()
if err != nil { log.Fatal(err) }
defer cleanup()
srv := &http.Server{Addr: ":8080", Handler: handler}
log.Fatal(srv.ListenAndServe())
}
```
## Multiple Build Variants (Prod vs Dev)
Use separate injector files with `//go:build` constraints to select different provider sets at build time.
```go
// wire_prod.go
//go:build wireinject && !dev
package main
func InitApp() (*App, func(), error) {
wire.Build(ProdSet, NewApp)
return nil, nil, nil
}
// wire_dev.go
//go:build wireinject && dev
package main
func InitApp() (*App, func(), error) {
wire.Build(DevSet, NewApp) // DevSet swaps real DB for in-memory SQLite
return nil, nil, nil
}
```
Use `-output_file_prefix` to write separate output files — both commands would otherwise overwrite the same `wire_gen.go`:
```bash
wire -tags dev -output_file_prefix=wire_gen_dev gen .
wire -output_file_prefix=wire_gen_prod gen .
```
Add build constraints to the generated files so only one compiles per build:
```go
// wire_gen_prod.go — add at the top (after wire writes it)
//go:build !dev
// wire_gen_dev.go — add at the top
//go:build dev
```
Commit both generated files. At build time, only the matching file is compiled.
## Cleanup-Heavy Graph
When several providers need shutdown coordination, wire's reverse-order cleanup is essential.
```go
// Providers return (T, func(), error)
func NewDBPool(cfg *Config) (*pgxpool.Pool, func(), error) {
pool, err := pgxpool.New(context.Background(), cfg.DSN)
if err != nil { return nil, nil, err }
return pool, func() { pool.Close() }, nil
}
func NewOTelExporter(cfg *Config) (*otlptrace.Exporter, func(), error) {
exp, err := otlptracegrpc.New(context.Background(), ...)
if err != nil { return nil, nil, err }
return exp, func() { exp.Shutdown(context.Background()) }, nil
}
func NewTracerProvider(exp *otlptrace.Exporter) (*trace.TracerProvider, func(), error) {
tp := trace.NewTracerProvider(trace.WithBatcher(exp))
return tp, func() { tp.Shutdown(context.Background()) }, nil
}
```
Wire generates shutdown in reverse: `TracerProvider` → `OTelExporter` → `DBPool`. Each cleanup runs before its dependencies shut down — guaranteeing in-flight spans are flushed before the exporter closes.
## Embedding Wire in a CLI
Wire produces a struct, not an app framework. You control the lifecycle:
```go
// wire.go
//go:build wireinject
package cmd
func InitServer(cfg *Config) (*http.Server, func(), error) {
wire.Build(InfraSet, ServiceSet, NewHTTPServer)
return nil, nil, nil
}
// cmd/serve.go
func runServe(cfg *Config) error {
srv, cleanup, err := InitServer(cfg)
if err != nil { return err }
defer cleanup()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatal(err)
}
}()
<-quit
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
return srv.Shutdown(ctx)
}
```
Unlike `fx.Run()`, wire does not manage the lifecycle loop. Implement signal handling and graceful shutdown explicitly. This is a feature for CLI tools that spin up short-lived services and need precise control over the shutdown sequence.
## Passing External Values to Wire
Values built before wire runs (parsed config, test doubles) become injector parameters:
```go
//go:build wireinject
// cfg is resolved outside the graph — treated as a provided *Config
func InitApp(cfg *Config) (*App, func(), error) {
wire.Build(InfraSet, ServiceSet, NewApp)
return nil, nil, nil
}
// main.go
cfg, err := config.Load()
if err != nil { log.Fatal(err) }
app, cleanup, err := InitApp(cfg)
```
The parameter `cfg *Config` satisfies any downstream provider that requests `*Config` — no `wire.Value` or extra set entry needed.
references/testing.md
# Testing — google/wire
Wire generates plain Go constructor calls, so tests work directly on the constructor layer — no container API to learn.
## Unit Tests: Plain Constructor Injection
The generated code has no wire dependency. Test constructors directly:
```go
func TestUserService_GetUser(t *testing.T) {
mockStore := &MockUserStore{users: map[int64]*User{1: &User{ID: 1, Name: "Alice"}}}
cache := newTestRedis(t)
svc := service.NewUserService(mockStore, cache)
u, err := svc.GetUser(context.Background(), 1)
require.NoError(t, err)
assert.Equal(t, "Alice", u.Name)
}
```
Pass mocks directly as constructor arguments. No wire, no container, no file to generate. This is the idiomatic approach for unit tests.
## Test Injectors: Swapping Providers
For integration or component tests where you want the full wired graph but with selected dependencies replaced, create a test-only injector in a `_test.go` file.
```go
// app_test.go
//go:build wireinject
package main
import (
"testing"
"github.com/google/wire"
)
// TestSet replaces real infra with in-memory fakes
var TestSet = wire.NewSet(
NewTestConfig,
NewInMemoryUserStore,
wire.Bind(new(repo.UserStore), new(*InMemoryUserStore)),
NewTestRedis,
)
func InitTestApp(t *testing.T) (*App, func(), error) {
wire.Build(TestSet, service.ServiceSet, NewApp)
return nil, nil, nil
}
```
```go
// app_integration_test.go
//go:build !wireinject // compiles when the wireinject tag is NOT set
package main
func TestApp_GetUser(t *testing.T) {
app, cleanup, err := InitTestApp(t)
require.NoError(t, err)
defer cleanup()
// test against the fully-wired app with fake dependencies
u, err := app.GetUser(context.Background(), 1)
require.NoError(t, err)
assert.NotNil(t, u)
}
```
Run `wire ./...` to generate `wire_gen.go` — the test injector is included because the `_test.go` file is compiled as part of the package during `go test`.
**Key pattern from upstream best practices:** Prefer creating a test-only provider set over passing mocks as injector arguments (though both work). The set approach keeps the test injector composable.
## Passing Mocks as Injector Arguments
An alternative to a test set: pass the mock directly as an injector parameter. Wire treats it as a pre-built provider.
```go
//go:build wireinject
func InitTestApp(store repo.UserStore) (*App, func(), error) {
wire.Build(config.ConfigSet, service.ServiceSet, NewApp)
return nil, nil, nil
}
// Test
func TestApp(t *testing.T) {
mock := &MockUserStore{}
app, cleanup, err := InitTestApp(mock)
require.NoError(t, err)
defer cleanup()
// ...
}
```
Use this form when you only need to replace one or two dependencies and a full `TestSet` is overkill.
## CI: Detecting Stale `wire_gen.go`
If `wire_gen.go` is not regenerated after a provider change, CI builds pass but the graph is wrong. Enforce freshness in CI:
```bash
# Option 1: re-run wire and check for diffs
wire ./...
git diff --exit-code -- '**/wire_gen.go'
```
```yaml
# .github/workflows/ci.yml
- name: Check wire_gen.go is up-to-date
run: |
go install github.com/google/wire/cmd/wire@v0.7.0
wire ./...
git diff --exit-code -- '**/wire_gen.go'
```
```bash
# Option 2: use wire check (verifies graph without regenerating)
wire check ./...
```
`wire check` exits non-zero if the graph is inconsistent but does **not** update `wire_gen.go`. Use it for a fast graph-validity check without modifying files.
## Testing Interface Bindings
`wire.Bind` can be used in test sets to bind a fake to the same interface:
```go
// Fake implements the same interface as the real provider
type FakeMailer struct{ sent []string }
func (f *FakeMailer) Send(to, body string) error { f.sent = append(f.sent, to); return nil }
var TestMailerSet = wire.NewSet(
NewFakeMailer,
wire.Bind(new(notification.Mailer), new(*FakeMailer)),
)
var TestSet = wire.NewSet(
TestMailerSet,
realServiceSet, // everything else is real
)
```
This keeps the test injector narrow — only the Mailer is faked; the rest of the graph is real.
## Table-Driven Tests Without Wire
Wire is an initialization tool. Once the object graph is built, table-driven tests on individual services need no wire involvement:
```go
func TestUserService(t *testing.T) {
cases := []struct {
name string
id int64
users map[int64]*User
want string
err bool
}{
{"found", 1, map[int64]*User{1: &User{Name: "Alice"}}, "Alice", false},
{"not found", 99, nil, "", true},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
svc := service.NewUserService(&MockUserStore{users: tc.users}, nil)
u, err := svc.GetUser(context.Background(), tc.id)
if tc.err { require.Error(t, err); return }
assert.Equal(t, tc.want, u.Name)
})
}
}
```
Wire has no role here — the injector was only needed to build the object graph in `main` (or in an integration test). Unit tests construct dependencies directly.