[teamai] Push 87 resource(s) from XingfenD
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
# Testing with uber-go/dig
|
||||
|
||||
dig containers are cheap to create. Build a fresh one per test, override what you need, drive the system with `Invoke`.
|
||||
|
||||
## Per-test container
|
||||
|
||||
```go
|
||||
func TestUserService_Create(t *testing.T) {
|
||||
c := dig.New()
|
||||
|
||||
fakeDB := &fakeDatabase{}
|
||||
require.NoError(t, c.Provide(func() Database { return fakeDB }))
|
||||
require.NoError(t, c.Provide(NewUserService))
|
||||
|
||||
require.NoError(t, c.Invoke(func(s *UserService) {
|
||||
err := s.Create(context.Background(), "alice@example.com")
|
||||
require.NoError(t, err)
|
||||
}))
|
||||
|
||||
require.Len(t, fakeDB.inserted, 1)
|
||||
}
|
||||
```
|
||||
|
||||
## Shared test wiring
|
||||
|
||||
For larger suites, factor the common providers into a helper:
|
||||
|
||||
```go
|
||||
func newTestContainer(t *testing.T, overrides ...func(*dig.Container)) *dig.Container {
|
||||
t.Helper()
|
||||
c := dig.New()
|
||||
require.NoError(t, c.Provide(NewTestLogger))
|
||||
require.NoError(t, c.Provide(NewInMemoryCache))
|
||||
require.NoError(t, c.Provide(func() Database { return &fakeDatabase{} }))
|
||||
require.NoError(t, c.Provide(NewUserService))
|
||||
|
||||
for _, override := range overrides {
|
||||
override(c)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func TestUserService_NotFound(t *testing.T) {
|
||||
c := newTestContainer(t, func(c *dig.Container) {
|
||||
// Replace the default DB with one that returns sql.ErrNoRows.
|
||||
require.NoError(t, c.Decorate(func(db Database) Database {
|
||||
return ¬FoundDB{Database: db}
|
||||
}))
|
||||
})
|
||||
|
||||
require.NoError(t, c.Invoke(func(s *UserService) {
|
||||
_, err := s.Get(context.Background(), "missing")
|
||||
require.ErrorIs(t, err, ErrUserNotFound)
|
||||
}))
|
||||
}
|
||||
```
|
||||
|
||||
`Decorate` is the cleanest way to swap a dependency in tests — the test reads almost like production wiring with one extra line.
|
||||
|
||||
## Validate the production graph in CI
|
||||
|
||||
```go
|
||||
func TestProductionGraph(t *testing.T) {
|
||||
c := dig.New(dig.DryRun(true))
|
||||
|
||||
// Replicate every Provide() from main()
|
||||
require.NoError(t, registerAll(c))
|
||||
|
||||
// Invoke the same root the production binary does
|
||||
require.NoError(t, c.Invoke(func(*http.Server, *Worker, *MetricsExporter) {}))
|
||||
}
|
||||
```
|
||||
|
||||
`DryRun(true)` skips constructor execution — the graph is validated structurally. This catches missing-provider and type-mismatch errors without spinning up real DB connections.
|
||||
|
||||
## Detecting cycles before deploy
|
||||
|
||||
A cyclic graph fails at the first `Invoke` (or at `Provide` time when `DeferAcyclicVerification` is off, which is the default):
|
||||
|
||||
```go
|
||||
func TestNoCycles(t *testing.T) {
|
||||
c := dig.New()
|
||||
require.NoError(t, registerAll(c))
|
||||
|
||||
err := c.Invoke(func(*App) {})
|
||||
require.False(t, dig.IsCycleDetected(err), "cycle in dependency graph: %v", err)
|
||||
}
|
||||
```
|
||||
|
||||
## Asserting a constructor's error path
|
||||
|
||||
When a constructor returns an error, dig wraps it with the dependency path. Use `dig.RootCause` to assert the original error:
|
||||
|
||||
```go
|
||||
func TestDBProvider_BadDSN(t *testing.T) {
|
||||
c := dig.New()
|
||||
require.NoError(t, c.Provide(func() *Config {
|
||||
return &Config{DSN: "not a dsn"}
|
||||
}))
|
||||
require.NoError(t, c.Provide(NewDB))
|
||||
|
||||
err := c.Invoke(func(*sql.DB) {})
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, dig.RootCause(err), "invalid connection string")
|
||||
}
|
||||
```
|
||||
|
||||
## Recovering from constructor panics
|
||||
|
||||
Wrap constructors that may panic on misuse so the test reports a typed error instead of crashing the runner:
|
||||
|
||||
```go
|
||||
c := dig.New(dig.RecoverFromPanics())
|
||||
|
||||
require.NoError(t, c.Provide(func() *App {
|
||||
panic("intentionally broken")
|
||||
}))
|
||||
|
||||
err := c.Invoke(func(*App) {})
|
||||
|
||||
var pe dig.PanicError
|
||||
require.True(t, errors.As(err, &pe))
|
||||
require.Contains(t, pe.Error(), "intentionally broken")
|
||||
```
|
||||
Reference in New Issue
Block a user