evals/evals.json
[
{
"id": 1,
"name": "lifecycle-not-init",
"description": "Tests use of fx.Lifecycle hooks instead of init() or constructor side effects for startup work",
"prompt": "In my Go service using uber-go/fx, I have a NewHTTPServer constructor that should listen on a port and serve requests. Where should I call srv.Serve(ln) — inside the constructor, in init(), or somewhere else?",
"trap": "Without the skill, the model calls srv.Serve in init() or directly inside the constructor (which would block boot), or writes a goroutine inside the constructor (which fires before lifecycle ordering applies). The right answer is OnStart with a goroutine.",
"assertions": [
{"id": "1.1", "text": "Injects fx.Lifecycle into NewHTTPServer"},
{"id": "1.2", "text": "Calls lc.Append with an fx.Hook (or fx.StartHook/StopHook) — OnStart starts the server, OnStop calls Shutdown"},
{"id": "1.3", "text": "OnStart launches srv.Serve inside a goroutine so the hook returns quickly"},
{"id": "1.4", "text": "Does NOT call srv.Serve directly inside the constructor"},
{"id": "1.5", "text": "Does NOT use init() to start the server"},
{"id": "1.6", "text": "OnStop calls srv.Shutdown(ctx) for graceful shutdown"}
]
},
{
"id": 2,
"name": "annotate-vs-fxout-struct",
"description": "Tests fx.Annotate as the modern way to add tags or interface bindings",
"prompt": "I have NewPostgresDB returning *PostgresDB in my Go app using uber-go/fx. I want consumers to ask for a Database interface, and I want this DB tagged with name:\"primary\" so I can add a replica later. Show me how.",
"trap": "Without the skill, the model writes a separate adapter constructor returning Database, or wraps the result in an fx.Out struct — missing fx.Annotate(NewPostgresDB, fx.As(new(Database)), fx.ResultTags(...)) which does both in one line.",
"assertions": [
{"id": "2.1", "text": "Uses fx.Annotate around NewPostgresDB"},
{"id": "2.2", "text": "Uses fx.As(new(Database)) inside the annotation to bind the interface"},
{"id": "2.3", "text": "Uses fx.ResultTags(`name:\"primary\"`) for the named tag"},
{"id": "2.4", "text": "Does NOT introduce a separate adapter/wrapper constructor"},
{"id": "2.5", "text": "Does NOT rewrite NewPostgresDB to return Database directly (since the original constructor stays untouched)"}
]
},
{
"id": 3,
"name": "module-organization",
"description": "Tests fx.Module for organizing related providers/invokes/decorators",
"prompt": "My Go application using uber-go/fx is growing — main.go now has dozens of fx.Provide calls for HTTP, database, metrics, and worker concerns mixed together. How should I reorganize this?",
"trap": "Without the skill, the model splits providers across several Go packages but keeps a flat list in main(), missing fx.Module which groups providers, invokes, and decorators under a name and lets decorators be module-scoped.",
"assertions": [
{"id": "3.1", "text": "Recommends fx.Module to group related options"},
{"id": "3.2", "text": "Shows at least 2 separate modules (e.g., HTTPModule, DatabaseModule)"},
{"id": "3.3", "text": "main() composes the modules via fx.New(HTTPModule, DatabaseModule, ...)"},
{"id": "3.4", "text": "Mentions OR demonstrates that fx.Decorate inside a module is scoped to that module"},
{"id": "3.5", "text": "Each module includes its own fx.Provide (and possibly fx.Invoke / fx.Decorate) calls"}
]
},
{
"id": 4,
"name": "supply-vs-provide",
"description": "Tests fx.Supply for pre-built values (config, secrets) instead of fx.Provide with a no-op constructor",
"prompt": "In my Go application using uber-go/fx, I parse a *Config from flags and load an API_KEY environment variable in main() before calling fx.New. How should I make these available to the rest of the graph?",
"trap": "Without the skill, the model writes fx.Provide(func() *Config { return cfg }) — a redundant constructor that just returns the existing value. fx.Supply does this without the boilerplate.",
"assertions": [
{"id": "4.1", "text": "Uses fx.Supply(cfg) (or fx.Supply with both values)"},
{"id": "4.2", "text": "Does NOT wrap the pre-built values in fx.Provide(func() *Config { return cfg })"},
{"id": "4.3", "text": "Mentions or demonstrates that fx.Supply makes pre-built values first-class graph members"},
{"id": "4.4", "text": "If both values are supplied as the same type or need a tag, optionally uses fx.Annotate within fx.Supply for tagging"}
]
},
{
"id": 5,
"name": "fxtest-with-populate",
"description": "Tests fxtest.New + fx.Populate for testing instead of raw fx.New + fx.Invoke",
"prompt": "I have a *UserService wired in my Go app using uber-go/fx. I want a unit test that pulls *UserService out of the graph (with a fake Database injected) and asserts behavior. Show me the minimal test.",
"trap": "Without the skill, the model uses fx.New + fx.Invoke(func(s *UserService) { ... }) — works but doesn't fail the test cleanly and has no Cleanup integration. fxtest.New + fx.Populate is idiomatic.",
"assertions": [
{"id": "5.1", "text": "Uses fxtest.New(t, ...) instead of fx.New"},
{"id": "5.2", "text": "Uses fx.Populate(&svc) to extract the *UserService from the graph"},
{"id": "5.3", "text": "Calls app.RequireStart() (or .Start) and app.RequireStop() (e.g., as t.Cleanup or defer)"},
{"id": "5.4", "text": "Provides a fake Database (interface) — does NOT use the real DB"},
{"id": "5.5", "text": "Does NOT use fx.Invoke as the primary mechanism for extracting the service"}
]
},
{
"id": 6,
"name": "replace-for-fakes",
"description": "Tests fx.Replace inside fxtest to swap a real dependency embedded in a module",
"prompt": "My Go app uses uber-go/fx with a ProductionModule that bundles all real wiring. In one integration test, I want to replace the real Database with an erroring fake — without rewriting the module. How?",
"trap": "Without the skill, the model rewrites the module (or copies it) to inject the fake — missing fx.Replace which overrides a previously-provided type without touching the module.",
"assertions": [
{"id": "6.1", "text": "Uses fx.Replace(...) inside fxtest.New (or fx.New) to override the Database"},
{"id": "6.2", "text": "Composes ProductionModule alongside fx.Replace (the module is reused unchanged)"},
{"id": "6.3", "text": "Uses fx.Annotate inside fx.Replace if needed for fx.As(new(Database)) binding"},
{"id": "6.4", "text": "Does NOT modify or duplicate the production module to inject the fake"},
{"id": "6.5", "text": "Mentions that fx.Replace is appropriate for tests (not production code)"}
]
},
{
"id": 7,
"name": "value-groups-handlers",
"description": "Tests value groups when many handler constructors must contribute to one slice",
"prompt": "In my Go HTTP server using uber-go/fx, I want every NewXxxHandler constructor to register itself with the router automatically — no manual list of handlers in main(). I have NewUserHandler, NewPostHandler, NewHealthHandler. The router consumes []http.Handler. Wire this.",
"trap": "Without the skill, the model assembles a slice manually in main() or writes one constructor that builds all handlers — missing the group:\"...\" tag pattern that keeps producers and the consumer decoupled.",
"assertions": [
{"id": "7.1", "text": "Each handler is registered with fx.Annotate(... fx.ResultTags(`group:\"routes\"`)) (or via an fx.Out struct with a group tag)"},
{"id": "7.2", "text": "The router (or server) consumes a fx.In with []http.Handler tagged group:\"routes\""},
{"id": "7.3", "text": "Optionally uses fx.As(new(http.Handler)) inside the annotation if the constructor returns a concrete type"},
{"id": "7.4", "text": "Does NOT manually maintain a slice of handlers in main()"},
{"id": "7.5", "text": "Does not assert ordering of the resulting slice (or explicitly notes order is unspecified)"}
]
},
{
"id": 8,
"name": "logger-fxevent-zap",
"description": "Tests fx.WithLogger + fxevent.ZapLogger to route fx events through the app's structured logger",
"prompt": "My Go service using uber-go/fx logs everything through *zap.Logger. The default fx output goes to stderr in a different format and is noisy in production. How do I route fx's own events (provide/invoke/start/stop) through my zap logger?",
"trap": "Without the skill, the model suggests overriding os.Stderr or grepping logs — missing fx.WithLogger which lets you provide an fxevent.Logger backed by zap.",
"assertions": [
{"id": "8.1", "text": "Uses fx.WithLogger(...) as an fx.New option"},
{"id": "8.2", "text": "The provided fxevent.Logger is &fxevent.ZapLogger{Logger: log}"},
{"id": "8.3", "text": "The logger inside fx.WithLogger receives the *zap.Logger as a parameter (so fx wires it from the graph)"},
{"id": "8.4", "text": "Does NOT redirect stderr or modify global log output"},
{"id": "8.5", "text": "May mention fx.NopLogger as an option to silence fx events"}
]
},
{
"id": 9,
"name": "manual-lifecycle-cli",
"description": "Tests app.Start / app.Done / app.Stop for embedding fx in a larger program",
"prompt": "I'm building a Go CLI tool that has an interactive sub-command and a serve sub-command. I want the serve sub-command to spin up an fx graph, start it, wait for SIGINT, and shut down — but I don't want fx hijacking the entire process via app.Run() (because the CLI may resume to other work after). How do I drive fx manually?",
"trap": "Without the skill, the model calls app.Run() and then can't return to the CLI — missing manual Start/Done/Stop, which is exactly what the user is asking for.",
"assertions": [
{"id": "9.1", "text": "Uses app.Start(ctx) explicitly with a context (often timeout-bounded)"},
{"id": "9.2", "text": "Waits on app.Done() (or a select including parent context cancellation) instead of calling app.Run()"},
{"id": "9.3", "text": "Uses app.Stop(ctx) explicitly with a context"},
{"id": "9.4", "text": "Does NOT recommend app.Run() as the primary mechanism for this scenario"},
{"id": "9.5", "text": "Mentions that app.Err() can validate wiring without starting"}
]
},
{
"id": 10,
"name": "onstart-non-blocking",
"description": "Tests that long-running OnStart work is launched in a goroutine, not run synchronously",
"prompt": "In my Go app using uber-go/fx, the OnStart hook for a worker calls a method that runs forever (consuming jobs from a queue until the app stops). Show me the OnStart implementation.",
"trap": "Without the skill, the model calls the long-running method synchronously inside OnStart — which hangs startup. The right pattern is to spawn a goroutine and return nil quickly.",
"assertions": [
{"id": "10.1", "text": "OnStart launches the long-running method inside a goroutine"},
{"id": "10.2", "text": "OnStart itself returns nil (or an error) quickly without waiting for the worker to finish"},
{"id": "10.3", "text": "OnStop signals the worker to stop (closing a channel, calling Cancel, etc.) and waits for it to drain"},
{"id": "10.4", "text": "Does NOT call the long-running method synchronously inside OnStart"},
{"id": "10.5", "text": "Mentions that a blocking OnStart would hang the boot / dependent hooks"}
]
},
{
"id": 11,
"name": "fx-when-not-dig",
"description": "Tests recommending raw dig (instead of fx) when the user does not need lifecycle / app boot",
"prompt": "I'm writing a one-shot Go CLI command that builds a small object graph (parses input, creates a few services, calls one of them, exits). I'm reading about uber-go/fx but it seems heavy. Should I use fx for this?",
"trap": "Without the skill, the model unconditionally recommends fx — missing that for one-shot programs without lifecycle, raw uber-go/dig is the lighter, simpler choice.",
"assertions": [
{"id": "11.1", "text": "Recommends raw uber-go/dig (or notes fx is overkill for this case)"},
{"id": "11.2", "text": "Mentions that fx is built on dig — the wiring patterns are nearly identical"},
{"id": "11.3", "text": "Mentions that fx adds value when the program needs lifecycle hooks, signal handling, or modular composition"},
{"id": "11.4", "text": "Does NOT recommend introducing fx.Lifecycle/fx.Module to a one-shot program"},
{"id": "11.5", "text": "May recommend manual constructor injection if the graph is very small"}
]
}
]
references/recipes.md
# Recipes — uber-go/fx
End-to-end examples that go beyond the SKILL.md basics. Each recipe is self-contained and shows a real wiring problem.
## Full HTTP service with database, metrics, and graceful shutdown
```go
package main
import (
"context"
"database/sql"
"fmt"
"net"
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go.uber.org/fx"
"go.uber.org/fx/fxevent"
"go.uber.org/zap"
)
func main() {
fx.New(
fx.Provide(
NewConfig,
NewLogger,
NewDatabase,
NewMetricsRegistry,
),
DatabaseModule,
HTTPModule,
MetricsModule,
fx.WithLogger(func(log *zap.Logger) fxevent.Logger {
return &fxevent.ZapLogger{Logger: log}
}),
fx.StartTimeout(30 * time.Second),
fx.StopTimeout(30 * time.Second),
).Run()
}
var DatabaseModule = fx.Module("database",
fx.Provide(
NewUserRepository,
NewPostRepository,
),
fx.Decorate(func(log *zap.Logger) *zap.Logger {
return log.Named("db")
}),
)
var HTTPModule = fx.Module("http",
fx.Provide(
NewRouter,
NewHTTPServer,
// Each handler joins the "routes" group.
AsRoute(NewUserHandler),
AsRoute(NewPostHandler),
AsRoute(NewHealthHandler),
),
fx.Invoke(func(*http.Server) {}), // forces server to be built
)
var MetricsModule = fx.Module("metrics",
fx.Provide(NewPrometheusHandler),
fx.Invoke(RegisterMetrics),
)
// Helper to register a handler with the "routes" group.
func AsRoute(ctor any) any {
return fx.Annotate(
ctor,
fx.As(new(Route)),
fx.ResultTags(`group:"routes"`),
)
}
type Route interface {
Pattern() string
http.Handler
}
type RouterParams struct {
fx.In
Routes []Route `group:"routes"`
}
func NewRouter(p RouterParams) *http.ServeMux {
mux := http.NewServeMux()
for _, r := range p.Routes {
mux.Handle(r.Pattern(), r)
}
return mux
}
func NewHTTPServer(lc fx.Lifecycle, log *zap.Logger, mux *http.ServeMux, cfg *Config) *http.Server {
srv := &http.Server{
Addr: cfg.Addr,
Handler: mux,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
}
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
ln, err := net.Listen("tcp", srv.Addr)
if err != nil {
return fmt.Errorf("listen %s: %w", srv.Addr, err)
}
go func() {
if err := srv.Serve(ln); err != nil && err != http.ErrServerClosed {
log.Error("server error", zap.Error(err))
}
}()
log.Info("listening", zap.String("addr", srv.Addr))
return nil
},
OnStop: func(ctx context.Context) error {
log.Info("shutting down")
return srv.Shutdown(ctx)
},
})
return srv
}
```
## Background worker with graceful drain
```go
type Worker struct {
log *zap.Logger
queue chan Job
done chan struct{}
}
func NewWorker(lc fx.Lifecycle, log *zap.Logger) *Worker {
w := &Worker{
log: log,
queue: make(chan Job, 100),
done: make(chan struct{}),
}
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
go w.run()
return nil
},
OnStop: func(ctx context.Context) error {
close(w.queue) // signal "no more jobs"
select {
case <-w.done:
w.log.Info("worker drained cleanly")
return nil
case <-ctx.Done():
w.log.Warn("worker stop timeout")
return ctx.Err()
}
},
})
return w
}
func (w *Worker) run() {
defer close(w.done)
for job := range w.queue {
job.Do(w.log)
}
}
```
The worker honors the stop context — under a 30-second `fx.StopTimeout` it has 30 seconds to drain. Beyond that, fx reports the timeout and the process exits.
## Multiple implementations of the same interface
Use named annotations + `fx.As` to register two `Cache` implementations and inject them by name:
```go
fx.Provide(
fx.Annotate(
NewRedisCache,
fx.As(new(Cache)),
fx.ResultTags(`name:"redis"`),
),
fx.Annotate(
NewMemcachedCache,
fx.As(new(Cache)),
fx.ResultTags(`name:"memcached"`),
),
)
type ServiceParams struct {
fx.In
Primary Cache `name:"redis"`
Fallback Cache `name:"memcached"`
}
```
## fx.Supply for config and secrets
```go
func main() {
cfg := mustLoadConfig() // parsed flags + env, before fx
secret := os.Getenv("API_KEY")
fx.New(
fx.Supply(cfg), // *Config available everywhere
fx.Supply(fx.Annotate(secret, fx.ResultTags(`name:"apikey"`))),
fx.Provide(NewLogger, NewAPIClient),
fx.Invoke(run),
).Run()
}
func NewAPIClient(cfg *Config, p struct {
fx.In
APIKey string `name:"apikey"`
}) *APIClient {
return &APIClient{baseURL: cfg.APIBaseURL, key: p.APIKey}
}
```
`fx.Supply` makes pre-built values first-class graph members. It is shorter and clearer than `fx.Provide(func() *Config { return cfg })`.
## Module-scoped decorator
```go
var WorkerModule = fx.Module("worker",
fx.Provide(NewWorker, NewJobQueue),
// Inside this module, *zap.Logger is automatically named "worker".
fx.Decorate(func(log *zap.Logger) *zap.Logger {
return log.Named("worker")
}),
)
var APIModule = fx.Module("api",
fx.Provide(NewServer, NewRouter),
fx.Decorate(func(log *zap.Logger) *zap.Logger {
return log.Named("api")
}),
)
```
The two modules see different loggers — there is no shared mutation of the parent value.
## Optional dependency for tracing
```go
type ServerParams struct {
fx.In
Logger *zap.Logger
Tracer trace.Tracer `optional:"true"`
}
func NewServer(p ServerParams) *Server {
s := &Server{log: p.Logger}
if p.Tracer == nil {
s.tracer = trace.NewNoopTracerProvider().Tracer("noop")
} else {
s.tracer = p.Tracer
}
return s
}
```
Reach for `optional` only when the dependency is genuinely optional. A missing core service hidden behind `optional` becomes a nil-pointer panic at first use.
## Manual lifecycle for embedding fx in a CLI
When fx is one component inside a larger program (a CLI tool, a test runner), drive Start/Stop yourself instead of calling `Run()`:
```go
func runFxApp(parent context.Context) error {
app := fx.New(
fx.Provide(NewConfig, NewLogger, NewWorker),
fx.Invoke(func(*Worker) {}),
)
if err := app.Err(); err != nil {
return fmt.Errorf("wire: %w", err)
}
startCtx, cancel := context.WithTimeout(parent, 30*time.Second)
defer cancel()
if err := app.Start(startCtx); err != nil {
return fmt.Errorf("start: %w", err)
}
select {
case <-parent.Done():
case <-app.Done(): // SIGINT/SIGTERM
}
stopCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
return app.Stop(stopCtx)
}
```
`app.Err()` validates wiring without starting — useful for `--check` style flags.
## Custom event logger that filters noise
```go
type ProductionLogger struct {
inner *fxevent.ZapLogger
}
func (l *ProductionLogger) LogEvent(e fxevent.Event) {
switch e.(type) {
case *fxevent.Provided, *fxevent.Supplied, *fxevent.Decorated:
return // drop the per-Provide chatter
default:
l.inner.LogEvent(e)
}
}
fx.New(
fx.Provide(NewZapLogger),
fx.WithLogger(func(log *zap.Logger) fxevent.Logger {
return &ProductionLogger{inner: &fxevent.ZapLogger{Logger: log}}
}),
)
```
In production, filtering provide/decorate noise leaves only lifecycle (start/stop) events and errors — much easier to audit.
references/testing.md
# Testing with uber-go/fx
`go.uber.org/fx/fxtest` integrates fx applications with `*testing.T`: errors fail the test instead of crashing the process, and lifecycle teardown is registered automatically.
## Pulling a value out of the graph with `fx.Populate`
```go
func TestUserService_Create(t *testing.T) {
var svc *UserService
app := fxtest.New(t,
fx.Provide(
func() Database { return &fakeDatabase{} },
NewUserService,
),
fx.Populate(&svc),
)
defer app.RequireStop()
app.RequireStart()
require.NoError(t, svc.Create(context.Background(), "alice@example.com"))
}
```
`fx.Populate(&svc)` fills `svc` with the value the graph would resolve. It replaces ad-hoc `fx.Invoke(func(s *UserService) { svc = s })` patterns.
## `fx.Replace` to swap a real dependency for a fake
```go
func TestServer_HandlesDBError(t *testing.T) {
var srv *http.Server
fakeDB := &erroringDatabase{}
app := fxtest.New(t,
ProductionModule, // the real wiring
fx.Replace(fx.Annotate(fakeDB, fx.As(new(Database)))),
fx.Populate(&srv),
)
defer app.RequireStop()
app.RequireStart()
// Drive the server with a fake DB
rec := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/users", nil)
srv.Handler.ServeHTTP(rec, req)
require.Equal(t, http.StatusInternalServerError, rec.Code)
}
```
`fx.Replace` works even when the original provider is buried inside a module — it overrides the resolved type without rewriting the module.
## Standalone lifecycle for a unit test
`fxtest.NewLifecycle(t)` gives you an `fx.Lifecycle` outside the `fx.New` machinery, useful for testing a single constructor that registers hooks:
```go
func TestWorker_StartStop(t *testing.T) {
lc := fxtest.NewLifecycle(t)
worker := NewWorker(lc, zaptest.NewLogger(t))
require.NotNil(t, worker)
lc.RequireStart() // runs OnStart hooks
require.True(t, worker.IsRunning())
lc.RequireStop() // runs OnStop hooks
require.False(t, worker.IsRunning())
}
```
This is the lightest test for a constructor — no full graph, no `fx.New`.
## Asserting wire-time errors
```go
func TestWiring_MissingDependency(t *testing.T) {
app := fx.New(
fx.Provide(NewServer), // depends on *sql.DB which is not provided
fx.NopLogger,
)
require.Error(t, app.Err())
require.Contains(t, app.Err().Error(), "missing type: *sql.DB")
}
```
Use `fx.New` (not `fxtest.New`) when you _expect_ the wiring to fail — `fxtest.New` would call `t.Fatal`.
## Validating the production graph in CI
```go
func TestProductionGraph(t *testing.T) {
app := fx.New(
ProductionOptions(), // every fx.Provide / fx.Module the binary uses
fx.NopLogger,
)
require.NoError(t, app.Err())
}
```
`fx.New` validates the type graph without starting. The test fails before deploy on any missing-provider, cycle, or annotation mismatch.
## Test logger that captures fx events
When you want to assert on lifecycle behavior, route fx events into an in-memory observer:
```go
// go.uber.org/zap/zaptest/observer
core, recorded := observer.New(zap.InfoLevel)
log := zap.New(core)
app := fxtest.New(t,
fx.WithLogger(func() fxevent.Logger {
return &fxevent.ZapLogger{Logger: log}
}),
fx.Provide(NewWorker),
fx.Invoke(func(*Worker) {}),
)
defer app.RequireStop()
app.RequireStart()
require.NotEmpty(t, recorded.FilterMessage("OnStart hook executed").All())
```
## Testing a lifecycle hook in isolation
If a constructor returns a value _and_ registers a hook, you often want to test both halves:
```go
func TestNewServer_OnStartFailsBindError(t *testing.T) {
// Bind a port so :0 is unavailable... no, simpler: pre-bind and pass that addr
listener, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
defer listener.Close()
addr := listener.Addr().String()
cfg := &Config{Addr: addr}
lc := fxtest.NewLifecycle(t)
NewHTTPServer(lc, zaptest.NewLogger(t), cfg)
// Use Start directly (not RequireStart) so we can assert the error.
require.Error(t, lc.Start(context.Background()))
}
```