[teamai] Push 87 resource(s) from XingfenD
This commit is contained in:
@@ -0,0 +1 @@
|
||||
XingfenD
|
||||
@@ -0,0 +1,229 @@
|
||||
---
|
||||
name: golang-google-wire
|
||||
description: "Compile-time dependency injection in Golang using google/wire — wire.NewSet, wire.Build, wire.Bind (interface→concrete), wire.Struct, wire.Value, wire.InterfaceValue, wire.FieldsOf, cleanup functions, //go:build wireinject injector files, and generated wire_gen.go. Apply when using or adopting google/wire, when the codebase imports `github.com/google/wire`, or when wiring an application graph at compile time via `wire.Build`. For runtime DI with reflection, see `samber/cc-skills-golang@golang-uber-dig` skill."
|
||||
user-invocable: true
|
||||
license: MIT
|
||||
compatibility: Designed for Claude Code or similar AI coding agents, and for projects using Golang.
|
||||
metadata:
|
||||
author: samber
|
||||
version: "1.0.6"
|
||||
openclaw:
|
||||
emoji: "🪡"
|
||||
homepage: https://github.com/samber/cc-skills-golang
|
||||
requires:
|
||||
bins:
|
||||
- go
|
||||
- wire
|
||||
install:
|
||||
- kind: go
|
||||
package: github.com/google/wire/cmd/wire@latest
|
||||
bins: [wire]
|
||||
skill-library-version: "0.7.0"
|
||||
allowed-tools: Read Edit Write Glob Grep Bash(go:*) Bash(golangci-lint:*) Bash(git:*) Agent WebFetch mcp__context7__resolve-library-id mcp__context7__query-docs Bash(wire:*) Bash(godig:*) Bash(gopls:*) LSP mcp__gopls__*
|
||||
---
|
||||
|
||||
**Persona:** You are a Go architect using wire for compile-time DI. You let the compiler catch missing dependencies, treat `wire_gen.go` as committed source, and re-run `wire ./...` after every graph change.
|
||||
|
||||
**Dependencies:**
|
||||
|
||||
- wire: `go install github.com/google/wire/cmd/wire@latest`
|
||||
|
||||
# Using google/wire for Compile-Time Dependency Injection in Go
|
||||
|
||||
Code-generation DI toolkit. Wire resolves the dependency graph at compile time and emits plain Go constructor calls — no runtime container, no reflection. Errors appear when you run `wire ./...`, not at first request.
|
||||
|
||||
Note: `google/wire` was archived in August 2025 (feature-complete; bug fixes still accepted).
|
||||
|
||||
**Official Resources:** [pkg.go.dev](https://pkg.go.dev/github.com/google/wire) · [github.com/google/wire](https://github.com/google/wire) · [User Guide](https://github.com/google/wire/blob/main/docs/guide.md) · [Best Practices](https://github.com/google/wire/blob/main/docs/best-practices.md)
|
||||
|
||||
This skill is not exhaustive. Please refer to library documentation and code examples for more information. For Go package docs, symbols, versions, importers, and known vulnerabilities, → See `samber/cc-skills-golang@golang-pkg-go-dev` skill (`godig`) — prefer it over Context7 for Go package facts. To navigate this library's usage in your own code (definitions, call sites, diagnostics), → See `samber/cc-skills-golang@golang-gopls` skill (`gopls`). Context7 remains a fallback for docs not indexed on pkg.go.dev.
|
||||
|
||||
```bash
|
||||
go get -tool github.com/google/wire/cmd/wire@latest
|
||||
go get github.com/google/wire
|
||||
```
|
||||
|
||||
## wire vs. Runtime DI
|
||||
|
||||
| Concern | wire | dig / fx / samber/do |
|
||||
| ----------------- | ------------------------- | ---------------------- |
|
||||
| Resolution | Compile time (codegen) | Runtime (reflection) |
|
||||
| Error detection | `wire ./...` fails | First `Invoke`/startup |
|
||||
| Runtime container | None — plain Go calls | Present |
|
||||
| Lifecycle hooks | Not built in | fx: OnStart/OnStop |
|
||||
| Generated files | `wire_gen.go` (committed) | None |
|
||||
|
||||
For lifecycle, lazy loading, and a full matrix see `samber/cc-skills-golang@golang-dependency-injection`.
|
||||
|
||||
## Providers
|
||||
|
||||
A provider is any Go function — inputs are dependencies, outputs are provided types. Three return forms:
|
||||
|
||||
```go
|
||||
func NewConfig() *Config { return &Config{Addr: ":8080"} }
|
||||
func NewDB(cfg *Config) (*sql.DB, error) { return sql.Open("postgres", cfg.DSN) }
|
||||
func NewRedis(cfg *Config) (*redis.Client, func(), error) { // cleanup chained in reverse order
|
||||
c := redis.NewClient(&redis.Options{Addr: cfg.RedisAddr})
|
||||
return c, func() { c.Close() }, nil
|
||||
}
|
||||
```
|
||||
|
||||
## Provider Sets
|
||||
|
||||
`wire.NewSet` groups providers for reuse. Sets can reference other sets.
|
||||
|
||||
```go
|
||||
// infra/wire.go
|
||||
var InfraSet = wire.NewSet(
|
||||
NewConfig,
|
||||
NewDB,
|
||||
NewRedis,
|
||||
)
|
||||
|
||||
// service/wire.go
|
||||
var ServiceSet = wire.NewSet(
|
||||
NewUserRepo,
|
||||
NewUserService,
|
||||
wire.Bind(new(UserStore), new(*UserRepo)), // interface binding
|
||||
)
|
||||
```
|
||||
|
||||
Keep sets small: library sets expose a stable surface (adding inputs or removing outputs breaks downstream injectors). One set per package is a useful default.
|
||||
|
||||
## Injectors and `//go:build wireinject`
|
||||
|
||||
The injector file declares the initialization function. Wire generates its body into `wire_gen.go` and replaces the stub.
|
||||
|
||||
```go
|
||||
//go:build wireinject
|
||||
|
||||
package main
|
||||
|
||||
import "github.com/google/wire"
|
||||
|
||||
// Wire generates the body of this function.
|
||||
func InitApp() (*App, func(), error) {
|
||||
wire.Build(InfraSet, ServiceSet, NewApp)
|
||||
return nil, nil, nil // replaced by codegen
|
||||
}
|
||||
```
|
||||
|
||||
The `//go:build wireinject` tag prevents the stub from being compiled into the binary — only `wire_gen.go` (which has no such tag) makes it through `go build`. Without this tag, both files define the same function, causing a compile error.
|
||||
|
||||
Alternative syntax when a dummy return is inconvenient:
|
||||
|
||||
```go
|
||||
func InitApp() (*App, func(), error) {
|
||||
panic(wire.Build(InfraSet, ServiceSet, NewApp))
|
||||
}
|
||||
```
|
||||
|
||||
## Interface Bindings
|
||||
|
||||
Wire forbids implicit interface satisfaction — you must declare bindings explicitly so the graph is unambiguous when multiple types implement the same interface.
|
||||
|
||||
```go
|
||||
var Set = wire.NewSet(
|
||||
NewPostgresUserRepo,
|
||||
wire.Bind(new(UserStore), new(*PostgresUserRepo)), // tell wire: *PostgresUserRepo satisfies UserStore
|
||||
)
|
||||
```
|
||||
|
||||
Explicit bindings prevent graph breakage when a new type implementing the same interface is added elsewhere.
|
||||
|
||||
## Struct Providers and Values
|
||||
|
||||
`wire.Struct` fills struct fields from the graph without a manual constructor. Tag fields `wire:"-"` to exclude them.
|
||||
|
||||
```go
|
||||
wire.Struct(new(Server), "Logger", "DB") // inject named fields
|
||||
wire.Struct(new(Server), "*") // inject all non-excluded fields
|
||||
wire.Value(Foo{X: 42}) // constant expression (no fn calls / channels)
|
||||
wire.InterfaceValue(new(io.Reader), os.Stdin) // interface-typed literal
|
||||
wire.FieldsOf(new(Config), "DSN", "Addr") // promote struct fields as graph nodes
|
||||
```
|
||||
|
||||
See [advanced.md](references/advanced.md) for the `wire:"-"` exclusion tag and `wire.FieldsOf` details.
|
||||
|
||||
## Disambiguating Duplicate Types
|
||||
|
||||
Wire forbids two providers for the same type. Wrap the underlying type in distinct named types so each has exactly one provider:
|
||||
|
||||
```go
|
||||
type PrimaryDSN string
|
||||
type ReplicaDSN string
|
||||
```
|
||||
|
||||
## Full Application Example
|
||||
|
||||
```go
|
||||
// wire.go — injector, excluded from binary via build tag
|
||||
//go:build wireinject
|
||||
|
||||
package main
|
||||
|
||||
func InitApp() (*App, func(), error) {
|
||||
wire.Build(config.ConfigSet, infra.InfraSet, service.ServiceSet, NewApp)
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
// main.go
|
||||
func main() {
|
||||
app, cleanup, err := InitApp()
|
||||
if err != nil { log.Fatal(err) }
|
||||
defer cleanup()
|
||||
app.Run()
|
||||
}
|
||||
```
|
||||
|
||||
Wire generates `wire_gen.go` (plain Go, committed, DO NOT EDIT). For a full example with per-package sets, cleanup-heavy graphs, and generated output, see [recipes.md](references/recipes.md).
|
||||
|
||||
## Codegen Workflow
|
||||
|
||||
```bash
|
||||
wire ./... # regenerate all injectors in the module
|
||||
wire check ./... # validate graph without regenerating (fast CI check)
|
||||
```
|
||||
|
||||
Run `wire ./...` after every constructor signature change. Add `//go:generate go run github.com/google/wire/cmd/wire` to injector files so `go generate ./...` also works. Commit `wire_gen.go` — it must stay in sync for CI builds.
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Never edit `wire_gen.go` — it is overwritten on every `wire ./...` run. Treat it as a build artifact that happens to be committed; source of truth is the provider and injector files.
|
||||
2. Always add `//go:build wireinject` to injector files — omitting it causes duplicate-symbol compile errors because both the stub and the generated file define the same function.
|
||||
3. Use named types to distinguish values of the same underlying type — wire enforces one provider per type; named types like `type DSN string` let you have `PrimaryDSN` and `ReplicaDSN` coexist.
|
||||
4. Keep library provider sets minimal and backward-compatible — adding new required inputs breaks downstream injectors; removing outputs does too. Introduce only newly-created types in the same release.
|
||||
5. Return `(T, func(), error)` from cleanup providers and let wire chain them — wire generates the correct reverse-order cleanup and handles partial failures (if construction fails midway, only already-built cleanups run).
|
||||
6. Keep injector files focused — one function per file, one package import at a time. Fat injectors with dozens of `wire.Build` arguments are hard to reason about; delegate to per-package sets.
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
| Mistake | Fix |
|
||||
| --- | --- |
|
||||
| Editing `wire_gen.go` manually | Never edit it. Change providers or injectors and re-run `wire ./...`. |
|
||||
| Missing `//go:build wireinject` | Add the tag as the very first line of every injector file. |
|
||||
| Two providers returning `*sql.DB` | Wrap with a named struct type: `type PrimaryDB struct { *sql.DB }` — Wire does not distinguish pointer type aliases. |
|
||||
| Injecting an interface without `wire.Bind` | Add `wire.Bind(new(MyInterface), new(*MyImpl))` to the provider set. |
|
||||
| Forgetting to re-run `wire ./...` after changes | Run wire before `go build`; add it to `go generate` or a Makefile target. |
|
||||
| Calling `cleanup()` without guarding for nil | Wire returns nil cleanup on construction error; guard with `if cleanup != nil { defer cleanup() }`. |
|
||||
|
||||
## Testing
|
||||
|
||||
Wire generates plain Go constructors, so unit tests use manual injection — no container to clone or reset. For testing patterns (test injectors swapping real providers for fakes, CI stale-check for `wire_gen.go`), see [testing.md](references/testing.md).
|
||||
|
||||
## Further Reading
|
||||
|
||||
- [advanced.md](references/advanced.md) — cleanup chains, multiple injectors, set nesting, error catalogue, codegen flags, quick reference
|
||||
- [recipes.md](references/recipes.md) — HTTP server, multi-injector build, cleanup-heavy graph, CLI embedding
|
||||
- [testing.md](references/testing.md) — test injectors, fake bindings, CI stale check
|
||||
|
||||
## Cross-References
|
||||
|
||||
- → See `samber/cc-skills-golang@golang-dependency-injection` skill for DI concepts and library comparison
|
||||
- → See `samber/cc-skills-golang@golang-uber-dig` skill for runtime reflection-based DI without lifecycle
|
||||
- → See `samber/cc-skills-golang@golang-uber-fx` skill for runtime DI with lifecycle hooks, modules, and signal-aware Run()
|
||||
- → See `samber/cc-skills-golang@golang-samber-do` skill for generics-based DI without reflection
|
||||
- → See `samber/cc-skills-golang@golang-structs-interfaces` skill for interface design patterns
|
||||
- → See `samber/cc-skills-golang@golang-testing` skill for general testing patterns
|
||||
|
||||
If you encounter a bug or unexpected behavior in google/wire, open an issue at <https://github.com/google/wire/issues>.
|
||||
@@ -0,0 +1,142 @@
|
||||
[
|
||||
{
|
||||
"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"}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,198 @@
|
||||
# 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 |
|
||||
@@ -0,0 +1,264 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,173 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user