[teamai] Push 87 resource(s) from XingfenD

This commit is contained in:
2026-09-10 16:10:45 +08:00
parent 425c9c078a
commit 65c04def51
1314 changed files with 211681 additions and 0 deletions
@@ -0,0 +1,144 @@
# 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()))
}
```