Skip to content

mhiro2/seedling

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

123 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

seedling logo

seedling

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.

Go Reference DeepWiki Release CI MIT License


✨ Why seedling?

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, and Ref
  • ♻️ WithTx for 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/faker with 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.

πŸ“¦ Installation

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@latest

πŸš€ Quick Start

The shortest path is two steps: generate blueprints from your schema, then call InsertOne in a test.

  1. Generate blueprints from your schema

    # From SQL DDL
    seedling-gen sql --pkg testutil --out blueprints.go schema.sql

    Other input sources are supported (sqlc, GORM, ent, Atlas) -- see the Guide for the full list.

    This generates struct types, NewRegistry(), RegisterBlueprints(reg), deterministic Defaults for common scalar fields, relations, and Insert stubs. Fill in the // TODO callbacks 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 Defaults intentionally 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.

  2. 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.

🧩 Typical Use Cases

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.

Override a field

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()
_ = user

Reuse an existing parent row

Use 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()
_ = user

Auto-rollback per test

Wrap 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.

🩺 Diagnostics

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.

Inspect the plan before executing

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)
_ = result

Trace inserts at runtime

WithInsertLog 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)
    }),
)
_ = result

Clean up when transactions are not an option

If 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.

βš–οΈ Comparison

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

πŸ“‚ Examples

  • 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, and Generate
  • reuse-parent: reuse existing rows with Use
  • batch-insert: batch inserts with shared Ref dependencies and per-row SeqRef overrides
  • with-tx: database/sql transaction helper with seedling.WithTx
  • sqlc: wire blueprints to sqlc-generated query code
  • pgx transactions: use github.com/mhiro2/seedling/seedlingpgx with pgxpool.Pool or *pgx.Conn
  • GORM / ent / Atlas: use seedling-gen with -gorm, -ent, or -atlas flags to generate blueprints from your existing schema definitions

πŸ“š Learn More

πŸ“ License

MIT

About

Dependency-aware test data builder for Go that resolves foreign-key DB relationships automatically.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors