Dependency-aware test data builder for Go and SQL databases.
seedling lets tests create only the rows they need while automatically resolving foreign-key dependencies in the correct order. You provide the insert logic. seedling handles planning, FK assignment, and execution order.
Manually wiring FK dependencies across 4 tables:
func TestCreateTask(t *testing.T) {
company, err := db.InsertCompany(ctx, InsertCompanyParams{Name: "acme"})
if err != nil { t.Fatal(err) }
user, err := db.InsertUser(ctx, InsertUserParams{
Name: "alice", CompanyID: company.ID,
})
if err != nil { t.Fatal(err) }
project, err := db.InsertProject(ctx, InsertProjectParams{
Name: "renewal", CompanyID: company.ID,
})
if err != nil { t.Fatal(err) }
task, err := db.InsertTask(ctx, InsertTaskParams{
Title: "design", ProjectID: project.ID, AssigneeUserID: user.ID,
})
if err != nil { t.Fatal(err) }
_ = task
}With seedling, the graph is resolved automatically:
func TestCreateTask(t *testing.T) {
result := seedling.InsertOne[Task](t, db)
task := result.Root()
_ = task
}seedling handles FK ordering, graph expansion, and cleanup so your tests stay focused on what matters:
- πͺΆ Zero runtime dependencies in the core module; optional DB helpers live in companion packages
- π Automatic FK resolution with topological insert ordering and minimal graph expansion
- πΏ First-class relation kinds:
BelongsTo,HasMany,ManyToMany, plus composite keys - π§ Per-test overrides via
Set,Use, andRef - β»οΈ
WithTxfor auto-rollback transactions -- no manual cleanup - π Works with sqlc,
database/sql, pgx, GORM, ent, or any other DB handle you own - π² Deterministic fake data via
seedling/fakerwith multi-locale support (en, ja, zh, ko, de, fr)
For advanced features such as InsertMany, batch sharing, Only, When, and dry runs, see the Guide.
Add an import in your code, then let the toolchain record the dependency:
import "github.com/mhiro2/seedling"Use the same pattern for companion packages when you need them, for example seedling/faker (github.com/mhiro2/seedling/faker) or seedlingpgx (github.com/mhiro2/seedling/seedlingpgx).
Install the seedling-gen CLI (pick one):
# Homebrew (macOS / Linux) β [third-party tap](https://github.com/mhiro2/homebrew-tap)
brew install --cask mhiro2/tap/seedling-gen# Go toolchain
go install github.com/mhiro2/seedling/cmd/seedling-gen@latestThe shortest path is two steps: generate blueprints from your schema, then call InsertOne in a test.
-
Generate blueprints from your schema
# From SQL DDL seedling-gen sql --pkg testutil --out blueprints.go schema.sqlOther input sources are supported (sqlc, GORM, ent, Atlas) -- see the Guide for the full list.
This generates struct types,
NewRegistry(),RegisterBlueprints(reg), deterministicDefaultsfor common scalar fields, relations, and Insert stubs. Fill in the// TODOcallbacks with your DB logic:Insert: func(ctx context.Context, db seedling.DBTX, v Company) (Company, error) { return insertCompany(ctx, db, v) // your DB call },
Generated
Defaultsintentionally skip primary keys, relation FK fields, and unsupported custom types. They are meant to make the first insert usable with zero setup, not to satisfy every unique or business constraint automatically.The snippets below assume the generated package is named
testutil. For a runnable minimal version of this flow, see examples/quickstart. -
Use it in tests
func TestUser(t *testing.T) { reg := testutil.NewRegistry() result := seedling.NewSession[testutil.User](reg).InsertOne(t, db) user := result.Root() if user.ID == 0 { t.Fatal("expected user ID to be set") } if user.CompanyID == 0 { t.Fatal("expected company to be inserted automatically") } }
That is the entire happy path. The next section shows the handful of patterns you reach for once tests grow.
These three patterns cover the majority of real test code. Anything beyond them lives in the Guide.
Note
All snippets below assume the generated package (e.g. from seedling-gen --pkg testutil) is imported as testutil, and reg := testutil.NewRegistry() has been called. The package name is set by --pkg; rename to whatever fits your project.
Use Set when a test needs a specific column value:
result := seedling.NewSession[testutil.User](reg).InsertOne(t, db,
seedling.Set("Name", "alice"),
)
user := result.Root()
_ = userUse Use to bind a relation to a row you already inserted, instead of letting seedling create another one:
company := seedling.NewSession[testutil.Company](reg).InsertOne(t, db).Root()
result := seedling.NewSession[testutil.User](reg).InsertOne(t, db,
seedling.Use("company", company),
)
user := result.Root()
_ = userWrap the test in a transaction that rolls back automatically on cleanup. No manual deletion, no leaking state across tests.
func TestUser(t *testing.T) {
tx := seedling.WithTx(t, db) // rollback runs at t.Cleanup
user := seedling.NewSession[testutil.User](reg).InsertOne(t, tx).Root()
_ = user
}For a runnable example, see examples/with-tx. For pgx-based projects, seedlingpgx.WithTx provides the same workflow.
When a graph misbehaves, you usually want to answer one of three questions: what would be inserted, what was inserted, or how do I clean it up. seedling exposes one helper for each.
Use Build to construct the plan without inserting, then print the dependency tree or the dry-run insert order:
plan := seedling.NewSession[testutil.Task](reg).Build(t,
seedling.Ref("project", seedling.Set("Name", "renewal")),
)
t.Log(plan.DebugString()) // dependency tree
t.Log(plan.DryRunString()) // insert order + FK assignments
result := plan.Insert(t, db)
_ = resultWithInsertLog reports each insert step (table, primary key, FK assignments) as it happens:
result := seedling.NewSession[testutil.Task](reg).InsertOne(t, db,
seedling.WithInsertLog(func(log seedling.InsertLog) {
t.Logf("step %d: %s (fks: %v)", log.Step, log.Table, log.FKBindings)
}),
)
_ = resultIf you cannot use WithTx, call Result.Cleanup to delete inserted rows in reverse dependency order:
result := seedling.NewSession[testutil.User](reg).InsertOne(t, db)
t.Cleanup(func() { result.Cleanup(t, db) })The Guide lists the full set of debugging APIs, including BatchResult.NodeAt for InsertMany graphs.
| Tool | Main model | Strong at | Not designed for |
|---|---|---|---|
| seedling | Dependency-aware builders with DB callbacks | Per-test graph generation, automatic FK resolution, type-safe overrides, graph inspection, codegen | Bulk loading large static fixture files |
| eyo-chen/gofacto | Generic factory with explicit FK associations | Ergonomic zero-config field filling, WithOne/WithMany associations, multi-DB support |
Automatic graph resolution, minimal graph expansion |
| go-testfixtures/testfixtures | Fixture files loaded into DB | Stable predefined datasets for integration tests | Relation-aware per-test graph construction |
| bluele/factory-go | In-memory object factories | Flexible object construction and traits-like composition | Planning SQL insert order across FK graphs |
| brianvoe/gofakeit | Fake data generator | Realistic random values | Database insertion orchestration or relation expansion |
- basic: register blueprints and insert rows with automatic parent creation
- quickstart: generated-style
NewRegistry()/RegisterBlueprints(reg)flow that matches the README Quick Start - custom-defaults: customize values with
Set,With, andGenerate - reuse-parent: reuse existing rows with
Use - batch-insert: batch inserts with shared
Refdependencies and per-rowSeqRefoverrides - with-tx:
database/sqltransaction helper withseedling.WithTx - sqlc: wire blueprints to sqlc-generated query code
- pgx transactions: use
github.com/mhiro2/seedling/seedlingpgxwithpgxpool.Poolor*pgx.Conn - GORM / ent / Atlas: use
seedling-genwith-gorm,-ent, or-atlasflags to generate blueprints from your existing schema definitions
- Guide -- workflows, full option reference (
Only,When,InsertMany, batch sharing, ...), and integration patterns - Architecture -- internal pipeline design (planner, graph, executor)
- Agent Skill: seedling-gen CLI -- instructions for AI agents that need to choose the right generator mode and scaffold blueprints
- Agent Skill: seedling test setup -- instructions for AI agents that write Go tests using seedling blueprints
- pkg.go.dev API reference -- full type and function docs
MIT
