Documentation
¶
Overview ¶
Package dbtest provides the shared harness for database-gated tests.
Every gated test package needs its own database, reset and migrated, isolated from every other package's in-flight test. Go runs different packages' test binaries concurrently, so naively resetting one shared database races: one package's schema reset can drop the schema out from under another package's in-flight test, corrupting its fixture (the classic symptom is a migration-version table claiming versions whose tables no longer exist). `go test -p 1` does not fix it — that serializes builds, not the test binaries themselves.
NewIsolatedPool gives each package its OWN database, derived from one configured base database, so packages can no longer collide and a `go test ./...` run with the harness's environment variable set is reliable.
Operator prerequisite: the role in the configured DSN must have the CREATEDB privilege, because the helper creates those derived databases on demand. See docs/testing.md.
Example ¶
Example demonstrates a consuming application wiring its own gated-test harness from dbtest's public API: one Harness, built once with the application's environment variable and migrator. Each gated test package then calls harness.NewIsolatedPool(t, "<suffix>") from its own tests — which a runnable example cannot show, having no *testing.T.
// This file lives in package dbtest_test (a black-box test package), not
// dbtest itself. That is deliberate: it exercises dbtest through its
// exported API alone, so a helper that quietly leaned on an unexported
// identifier of its own package would fail to compile here. It doubles as
// the package's godoc example. Note it cannot prove importability from
// outside the module: Go's internal rule admits any importer under
// internal/'s parent, so this file would still compile if the package were
// nested under internal/. Cross-module importability is proven by
// Nestorage, which imports this path from its own test suites. This example
// needs no live database: New only wires an env var name and a Migrator
// together, and the migrator here is a no-op stand-in.
package main
import (
"context"
"fmt"
"github.com/ericfisherdev/nestcore/db/dbtest"
)
// noopMigrator satisfies dbtest.Migrator without touching a real database,
// standing in for an application's embedded-goose migrator in this example.
type noopMigrator struct{}
func (noopMigrator) Reset(context.Context, string) error { return nil }
func (noopMigrator) Up(context.Context, string) error { return nil }
// Example demonstrates a consuming application wiring its own gated-test
// harness from dbtest's public API: one Harness, built once with the
// application's environment variable and migrator. Each gated test package
// then calls harness.NewIsolatedPool(t, "<suffix>") from its own tests —
// which a runnable example cannot show, having no *testing.T.
func main() {
harness := dbtest.New("NESTOVA_TEST_DATABASE_URL", noopMigrator{})
fmt.Printf("%T\n", harness)
}
Output: *dbtest.Harness
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Harness ¶
type Harness struct {
// contains filtered or unexported fields
}
Harness runs database-gated tests for one calling application, against databases derived from one base DSN. Construct one with New — typically once per application, package-level in a test helper — and share it across that application's gated test packages.
func New ¶
New returns a Harness that reads its base DSN from envVar and resets and applies schema through migrator. It panics on an empty envVar or a nil migrator: both are programming errors, not conditions a caller can recover from at test time.
func (*Harness) DSN ¶
DSN returns the derived DSN for suffix without creating a pool, for the few tests that need the connection string itself (e.g. to drive a CLI). It performs the same creation and safety checks as NewIsolatedPool but does not reset or migrate.
func (*Harness) NewIsolatedPool ¶
NewIsolatedPool returns a pool against a database dedicated to the calling package — the configured database's name plus "_" plus suffix — freshly reset and migrated. It skips the test when h's environment variable is unset.
suffix must be a short, stable, package-identifying literal ("tasks", "auth", ...): it becomes part of a real database name, so two packages sharing a suffix would re-create the very race this helper removes.
The derived database is created on demand (CREATEDB required) and left in place between runs; only its schema is reset, on both setup and cleanup.
type Migrator ¶
type Migrator interface {
Reset(ctx context.Context, dsn string) error
Up(ctx context.Context, dsn string) error
}
Migrator resets and applies the calling application's schema to a derived test database. dbtest cannot depend on a migration runner directly: nestcore owns no migrations, and each application embeds its own set.
type Option ¶
type Option func(*options)
Option customizes NewIsolatedPool.
func WithPreReset ¶
func WithPreReset(hook PreResetHook) Option
WithPreReset registers a hook to run just before each migrator Reset.
type PreResetHook ¶
PreResetHook runs against the derived DSN immediately before every call to the migrator's Reset (both setup and cleanup). It exists for packages whose data can block a down-migration. Hooks are best-effort: they must not fail the test.