Documentation
¶
Overview ¶
Package dbtestkit. doc.go - Provides a generic, ORM-agnostic test container toolkit for integration tests that need a real database (MySQL, PostgreSQL, or SQLite).
dbtestkit is designed to be wired into a Go test package's TestMain function. It boots a database (in Docker for MySQL/PostgreSQL, or on local disk for SQLite), restores a pre-baked pristine SQL dump for millisecond resets between tests, and exposes a single Reset entry-point that returns the database to a known-good state before every test.
Configuration is entirely functional-options based — no environment variables. Every project-specific concern (schema creation, data seeding, engine construction, cache invalidation) is supplied by the caller via callbacks, so the library never imports your ORM or your domain objects.
Package dbtestkit. driver.go - Defines the set of supported database drivers and provides validation helpers.
Package dbtestkit. engine.go - Defines the minimal Engine abstraction the library needs to talk to the user's database for truncation, pings, and cache invalidation.
The library intentionally does NOT depend on any specific ORM (xorm, gorm, sqlx, …). Users adapt their ORM to this interface; an xorm adapter ships in the dbtestkit/xormadapter subpackage.
Package dbtestkit. environment.go - Defines the Environment struct that is passed to every user-supplied callback. It exposes the runtime context the caller needs (DSN, driver, project root, logger, engine handle) without leaking internal state.
Package dbtestkit. lifecycle.go - Orchestrates the test lifecycle. Exposes two public entry points: Run (called from TestMain) and Reset (called from each test). Everything else is internal plumbing.
The library uses a package-scoped global to share state between Run and Reset because Go's TestMain model is inherently package-scoped — the test binary boots one process per package, so a package-level global is the natural lifecycle boundary.
Package dbtestkit. logger.go - Provides the Logger interface and a default tree-style printer that mirrors the original casdoor test output format.
Package dbtestkit. options.go - Defines the functional-options API used to configure a test environment.
All configuration is supplied via WithXxx options — there are no environment variables. This keeps tests hermetic, makes the configuration discoverable via code completion, and avoids global state.
Package dbtestkit. project.go - Provides the helper that locates the project root directory by walking up the filesystem until a go.mod file is found.
Package dbtestkit. testing.go - Exposes a minimal set of internal hooks for use by tests in this package and downstream consumers that need to assert option application without spinning up a real container.
This file ships with the library (it is not a _test.go file) so the functions it exposes are part of the public API. They are clearly named "ForTesting" to discourage production use.
Index ¶
- func Reset[S stringish](t MinimalTandB, testNames ...S) error
- func ResetForTesting(t MinimalTandB, opts ...Option) error
- func Run(m RunM, driver DatabaseDriver, schemaInit SchemaInitializer, ...)
- func SetupForTesting(opts ...Option) (*Environment, Engine, DatabaseDriver, error)
- type CacheInvalidator
- type DataInitializer
- type DatabaseConfig
- type DatabaseDriver
- type DefaultLogger
- type Driver
- type Engine
- type EngineInitializer
- type Environment
- func (e *Environment) Context() context.Context
- func (e *Environment) DSN() string
- func (e *Environment) Database() DatabaseConfig
- func (e *Environment) Driver() Driver
- func (e *Environment) DriverConfig() DatabaseConfig
- func (e *Environment) Engine() Engine
- func (e *Environment) Logger() Logger
- func (e *Environment) ProjectRoot() string
- func (e *Environment) SQLitePath() string
- func (e *Environment) TestdataDir() string
- type Logger
- type MinimalTandB
- type NoopEngine
- type Option
- func WithCacheInvalidator(fn CacheInvalidator) Option
- func WithDataInitializer(fn DataInitializer) Optiondeprecated
- func WithDatabase(db DatabaseConfig) Option
- func WithDriver(d DatabaseDriver) Optiondeprecated
- func WithEngineInitializer(fn EngineInitializer) Optiondeprecated
- func WithGeneratePristine(force bool) Option
- func WithLogger(l Logger) Option
- func WithPristineDumpPath(path string) Option
- func WithProjectRoot(path string) Option
- func WithSQLitePath(path string) Option
- func WithSchemaInitializer(fn SchemaInitializer) Optiondeprecated
- func WithSeeders(seeders ...Seeder) Option
- func WithTestdataDir(dir string) Option
- type ResetStrategy
- type RunM
- type SchemaInitializer
- type Seeder
- type TestingConfig
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Reset ¶
func Reset[S stringish](t MinimalTandB, testNames ...S) error
Reset restores the database to its pristine state and runs any custom seeders. Call this at the top of every test that depends on a clean database.
testNames is an optional list of human-readable string labels (e.g. sub-test names) that will be echoed in the reset banner for log readability.
func ResetForTesting ¶
func ResetForTesting( t MinimalTandB, opts ...Option, ) error
ResetForTesting runs the package-internal resetDatabase function against a runtime state assembled from the given options + a previously-returned Environment/Engine/Driver triple. It does NOT call os.Exit and is safe to use from test code.
seeders and cacheInvalidator override whatever was supplied via opts.
func Run ¶
func Run( m RunM, driver DatabaseDriver, schemaInit SchemaInitializer, dataInit DataInitializer, engineInit EngineInitializer, opts ...Option, )
Run is the entry point called from TestMain. It boots the driver, runs the slow or fast setup path, executes the test suite, and tears everything down. On any setup error it prints the error and calls os.Exit(1) — TestMain has no other way to fail before tests run.
driver, schemaInit, dataInit, and engineInit are the four pieces of configuration every caller must supply — there is no meaningful default for "which database" or "how do I create your schema." Making them positional means a TestMain that omits one fails to compile, instead of failing on the first `go test` run with a "dbtestkit: WithXxx is required" string. Everything else (credentials, paths, seeders, logger, cache invalidation) is genuinely optional and stays in opts.
Example:
dbtestkit.Run(m, mysql.New(), tests.CasdoorSchemaInit, tests.CasdoorDataInit, tests.CasdoorEngineInit,
dbtestkit.WithDatabase(dbtestkit.DatabaseConfig{Database: "casdoor", Username: "root", Password: "casdoor"}),
dbtestkit.WithCacheInvalidator(tests.CasdoorCacheInvalidator),
dbtestkit.WithSeeders(seedFn),
)
If you already have call sites built against the old all-options form, they keep compiling unchanged: WithDriver / WithSchemaInitializer / WithDataInitializer / WithEngineInitializer are still accepted and simply overwrite the same fields (last write wins, same as any other Option).
func SetupForTesting ¶
func SetupForTesting(opts ...Option) (*Environment, Engine, DatabaseDriver, error)
SetupForTesting runs the package-internal setup function with a config derived from the given options. It returns the constructed Environment, Engine, and the resolved DatabaseDriver, exactly as the production Run function would see them.
Tests use this to exercise the fast/slow-path logic without invoking os.Exit (which Run does and which makes direct testing impossible).
Types ¶
type CacheInvalidator ¶
type CacheInvalidator func(env *Environment) error
CacheInvalidator is invoked before every reset to flush ORM-level caches. Return nil if your ORM has no cache.
type DataInitializer ¶
type DataInitializer func(env *Environment) error
DataInitializer is invoked once on the slow path to seed base data.
type DatabaseConfig ¶
type DatabaseConfig struct {
// Database is the name of the database to create inside the container.
Database string
// Username is the database user the engine will connect as.
Username string
// Password is the database user's password.
Password string
// Image is the Docker image reference, e.g. "mysql:lts" or "postgres:16-alpine".
Image string
// StartupTimeout is the maximum time to wait for the container to become
// ready. Zero means use the driver default.
StartupTimeout time.Duration
}
DatabaseConfig holds the credentials and image selection for a containerized database. Fields not relevant to a given driver (e.g. Image for SQLite) are ignored.
type DatabaseDriver ¶
type DatabaseDriver interface {
Driver() Driver
Start(ctx context.Context, env *Environment) (string, error)
RestoreDump(ctx context.Context, env *Environment, dumpPath string) error
GenerateDump(ctx context.Context, env *Environment, dumpPath string) error
Truncate(ctx context.Context, env *Environment) error
Stop(ctx context.Context, env *Environment) error
ResetStrategy() ResetStrategy
}
type DefaultLogger ¶
type DefaultLogger struct {
// contains filtered or unexported fields
}
DefaultLogger prints tree-style timing output to stdout, matching the format used by the original casdoor test utilities:
===> 🚀 Test Container Setup
├── Create MySQL Container: 420ms
└── ✅ Test Container Ready! 1.2s
func NewDefaultLogger ¶
func NewDefaultLogger(w io.Writer) *DefaultLogger
NewDefaultLogger returns a DefaultLogger writing to w. Pass nil to use os.Stdout.
func (*DefaultLogger) End ¶
func (l *DefaultLogger) End(name string, elapsed time.Duration)
End implements Logger.
type Driver ¶
type Driver string
Driver identifies a supported database backend.
Use one of the named constants below when configuring the environment via WithDriver. Custom drivers can be registered separately via the drivers subpackage.
func SupportedDrivers ¶
func SupportedDrivers() []Driver
SupportedDrivers returns the list of drivers built into the library.
Useful for documentation, CLI flag validation, or generating help text.
type Engine ¶
type Engine interface {
// Exec executes a statement that does not return rows.
Exec(query string, args ...any) (sql.Result, error)
// QueryString runs a query and returns each row as a map[string]string
// (column name → stringified value). Used for SHOW TABLES / sqlite_master.
QueryString(query string, args ...any) ([]map[string]string, error)
// Ping verifies the database connection is alive.
Ping() error
// Close releases the underlying connection pool.
Close() error
// ClearCache flushes any ORM-level in-memory cache.
//
// Return nil if the underlying ORM has no cache (e.g. plain *sql.DB).
ClearCache() error
}
Engine is the minimal database surface area the library requires.
It exists so the library can TRUNCATE tables, ping the DB, clear ORM-level caches, and close the connection — without importing any ORM. Implementations only need to forward these calls to the underlying *sql.DB, *xorm.Engine, *gorm.DB, etc.
type EngineInitializer ¶
type EngineInitializer func(env *Environment) (Engine, error)
EngineInitializer constructs the user's Engine from the DSN exposed on env. Called once per package setup; the returned Engine is reused for all tests.
type Environment ¶
type Environment struct {
// contains filtered or unexported fields
}
Environment is the runtime handle passed to every user callback (schema init, data init, engine init, cache invalidation, seeders).
It is intentionally read-only from the caller's perspective. The library populates it during the setup phase; callers consume its fields.
func EnvWithEngineForTesting ¶
func EnvWithEngineForTesting(env *Environment, eng Engine) *Environment
EnvWithEngineForTesting returns a shallow copy of env with the Engine field replaced. Used by driver tests to inject a test-specific Engine before calling Truncate.
func NewEnvironmentForTesting ¶
func NewEnvironmentForTesting(base *Environment, driverName Driver, testdataDir, sqlitePath string) *Environment
NewEnvironmentForTesting constructs an Environment with the minimal fields a driver needs to operate (driver name, testdata dir, sqlite path).
If base is non-nil, its other fields (DSN, project root, logger) are inherited; otherwise sensible defaults are used. This is intended for driver-level tests that want to exercise Start/Stop/RestoreDump/Truncate in isolation without invoking the full lifecycle.
func (*Environment) Context ¶
func (e *Environment) Context() context.Context
Context returns the lifecycle context bound to the current setup or reset operation. Callers should prefer this context over context.Background() so their work is cancelled when the test process exits.
func (*Environment) DSN ¶
func (e *Environment) DSN() string
DSN returns the connection string the engine should use to connect.
For MySQL/Postgres this is a network DSN; for SQLite this is a file:// URL. The value is only populated after the underlying container (or file) has been created.
func (*Environment) Database ¶
func (e *Environment) Database() DatabaseConfig
Database returns the database credentials and image configuration supplied via WithDatabase (or the driver's defaults if WithDatabase was not called).
Driver implementations use this to build container requests. User callbacks typically do not need it — they should use DSN() instead.
func (*Environment) Driver ¶
func (e *Environment) Driver() Driver
Driver returns the active database driver.
func (*Environment) DriverConfig ¶
func (e *Environment) DriverConfig() DatabaseConfig
DriverConfig is an alias for Database used by driver implementations. Kept as a separate method so the public API reads naturally for both audiences.
func (*Environment) Engine ¶
func (e *Environment) Engine() Engine
Engine returns the user-supplied Engine, or nil if it has not been constructed yet (e.g. during the schema-initialization phase).
func (*Environment) Logger ¶
func (e *Environment) Logger() Logger
Logger returns the active Logger.
func (*Environment) ProjectRoot ¶
func (e *Environment) ProjectRoot() string
ProjectRoot returns the absolute path to the project root (where go.mod lives).
func (*Environment) SQLitePath ¶
func (e *Environment) SQLitePath() string
SQLitePath returns the on-disk location of the SQLite database file.
Only meaningful when Driver() == DriverSQLite. Returns the empty string for other drivers.
func (*Environment) TestdataDir ¶
func (e *Environment) TestdataDir() string
TestdataDir returns the absolute path to the testdata directory.
Defaults to <projectRoot>/testdata. Override with WithTestdataDir.
type Logger ¶
type Logger interface {
// Step reports the elapsed time of an inner setup/reset step.
Step(name string, elapsed time.Duration)
// End reports the elapsed time of an outer setup/reset phase.
End(name string, elapsed time.Duration)
// Info emits an informational line (e.g. "==> 🚀 Test Container Setup").
Info(msg string)
// Warn emits a non-fatal warning.
Warn(msg string)
}
Logger is the logging surface used by the library. Implementations are free to forward to *testing.T, log/slog, zap, or any other sink.
type MinimalTandB ¶
type MinimalTandB interface {
Helper()
Logf(format string, args ...any)
Errorf(format string, args ...any)
Fatalf(format string, args ...any)
}
MinimalTandB is the subset of *testing.T / *testing.B the library needs. Defined here so callers can pass either without the library depending on the testing package at compile time (other than in tests).
type NoopEngine ¶
type NoopEngine struct{}
NoopEngine is a no-op Engine implementation intended for tests that want to exercise library plumbing without a real database. Production callers should provide a real engine via WithEngineInitializer.
func (NoopEngine) ClearCache ¶
func (NoopEngine) ClearCache() error
ClearCache satisfies the Engine interface.
func (NoopEngine) QueryString ¶
QueryString satisfies the Engine interface and returns an empty slice.
type Option ¶
type Option func(*config) error
Option configures the test environment.
func WithCacheInvalidator ¶
func WithCacheInvalidator(fn CacheInvalidator) Option
WithCacheInvalidator supplies an optional callback invoked before every reset.
Use this to flush ORM-level caches (e.g. xorm's Ristretto store) so that post-reset reads do not return stale pointers from the previous test.
func WithDataInitializer
deprecated
func WithDataInitializer(fn DataInitializer) Option
WithDataInitializer supplies the callback that seeds base data on the slow path.
Deprecated: pass it as Run's fourth positional argument instead. Kept for backward compatibility.
func WithDatabase ¶
func WithDatabase(db DatabaseConfig) Option
WithDatabase configures credentials and image for the containerized database. Required for MySQL and Postgres; ignored for SQLite.
func WithDriver
deprecated
func WithDriver(d DatabaseDriver) Option
WithDriver supplies the database driver implementation.
Deprecated: pass the driver as the second positional argument to Run instead — that form is checked by the compiler, so a missing driver is a build error instead of a runtime "WithDriver is required" failure. This option is kept only so existing call sites continue to compile; the last value set (whether by this option or by Run's positional argument) wins.
Pass the constructor result from the desired driver subpackage:
import "github.com/seyallius/gopherbox/dbtestkit/drivers/mysql"
dbtestkit.Run(m, mysql.New(), schemaInit, dataInit, engineInit,
dbtestkit.WithDatabase(dbCfg),
)
func WithEngineInitializer
deprecated
func WithEngineInitializer(fn EngineInitializer) Option
WithEngineInitializer supplies the callback that builds the Engine from the DSN exposed on the Environment.
Deprecated: pass it as Run's fifth positional argument instead. Kept for backward compatibility.
func WithGeneratePristine ¶
WithGeneratePristine forces the slow path on the next setup, regenerating the pristine dump file even if one already exists. Useful as a one-shot command (e.g. invoked by a `make pristine` target).
func WithLogger ¶
WithLogger replaces the default logger. Pass nil to disable output entirely.
func WithPristineDumpPath ¶
WithPristineDumpPath overrides the location of the pristine SQL dump file.
Defaults to <testdataDir>/<driver>-pristine.sql.
func WithProjectRoot ¶
WithProjectRoot explicitly sets the project root directory, bypassing the automatic go.mod lookup. Useful in environments where the working directory is not inside the module tree.
func WithSQLitePath ¶
WithSQLitePath overrides the on-disk location of the SQLite database file. SQLite driver only. Defaults to <testdataDir>/dbtestkit.sqlite.
func WithSchemaInitializer
deprecated
func WithSchemaInitializer(fn SchemaInitializer) Option
WithSchemaInitializer supplies the callback that creates tables.
Deprecated: pass it as Run's third positional argument instead; see WithDriver's doc comment for why. Kept for backward compatibility.
For xorm users this is typically engine.Sync2(&MyModel{}).
func WithSeeders ¶
WithSeeders appends custom seeder functions executed after the pristine state is restored on every reset. Repeatable; later calls append.
func WithTestdataDir ¶
WithTestdataDir overrides the testdata directory used to store auxiliary files (pristine dumps, pre-baked container tarballs, etc.).
Defaults to <projectRoot>/testdata.
type ResetStrategy ¶ added in v0.3.2
type ResetStrategy int
ResetStrategy dictates how the database is restored to a pristine state between tests.
const ( // ResetStrategyRestoreDump uses the driver's RestoreDump method (e.g., piping SQL or copying files). // This is the fastest approach for network databases like MySQL/Postgres. ResetStrategyRestoreDump ResetStrategy = iota // ResetStrategyTruncateAndSeed truncates all tables and re-runs the DataInitializer. // This is necessary for in-process databases like SQLite on Windows, where file locks // prevent replacing the database file while connections are open. ResetStrategyTruncateAndSeed )
type RunM ¶
type RunM interface {
Run() int
}
RunM is the subset of *testing.M the library needs. Defined here so the core package does not need to import "testing" at compile time (other than in test files).
type SchemaInitializer ¶
type SchemaInitializer func(env *Environment) error
SchemaInitializer is invoked once on the slow path to create tables. It receives the live Environment (engine may be nil if the user chose to construct the engine after schema sync — see WithEngineInitializer).
type Seeder ¶
type Seeder func(env *Environment) error
Seeder is invoked after the pristine state has been restored, on every reset. Use it to insert test-specific fixtures.
type TestingConfig ¶
type TestingConfig struct {
DriverName Driver
Database DatabaseConfig
SchemaInit SchemaInitializer
DataInit DataInitializer
EngineInit EngineInitializer
CacheInvalidator CacheInvalidator
Seeders []Seeder
PristineDumpPath string
TestdataDir string
SQLitePath string
ProjectRoot string
GeneratePristine bool
Logger Logger
}
TestingConfig is a read-only view of the internal config struct, exposed for tests that need to assert what options were applied. Production code should never use this type.
func ApplyOptionsForTesting ¶
func ApplyOptionsForTesting(opts ...Option) (*TestingConfig, error)
ApplyOptionsForTesting builds a TestingConfig from a list of options.
This is the only sanctioned way for tests to inspect option application without reimplementing the validation logic. Production callers should use Run, which performs the same validation internally.
Returns an error if the options fail validation (missing required fields, nil callbacks, etc.).