testing

package
v0.20.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 18, 2025 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package testing provides utilities for testing database logic in go-bricks applications. This package follows the design patterns from observability/testing, providing fluent APIs and in-memory fakes that eliminate the complexity of sqlmock and testify mocks.

The primary type is TestDB, which implements database.Querier and database.Interface with expectation-based mocking. Use TestDB for unit tests where you want to verify SQL queries and execution without needing a real database.

Resource Management

IMPORTANT: When using Query() or TestTx.Query(), callers MUST call defer rows.Close() immediately after obtaining rows to prevent resource leaks. The returned *sql.Rows is backed by a temporary *sql.DB that requires explicit cleanup.

Correct pattern:

rows, err := db.Query(ctx, "SELECT ...")
if err != nil {
    return err
}
defer rows.Close()  // REQUIRED - prevents resource leaks

A runtime.SetFinalizer provides a safety net for garbage collection, but this is non-deterministic and should NOT be relied upon. Always use defer rows.Close() for deterministic resource management.

For integration tests requiring actual database behavior, see the container helpers which wrap testcontainers for PostgreSQL and Oracle.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AssertCommitted

func AssertCommitted(t *testing.T, tx *TestTx)

AssertCommitted asserts that the transaction was committed.

Example:

tx := db.ExpectTransaction()
// ... execute test code ...
AssertCommitted(t, tx)

func AssertExecCount

func AssertExecCount(t *testing.T, db *TestDB, sqlPattern string, expected int)

AssertExecCount asserts that exactly N execs matching the SQL pattern were executed.

Example:

db := NewTestDB(dbtypes.PostgreSQL)
// ... execute batch insert that should affect 5 rows ...
AssertExecCount(t, db, "INSERT", 5)

func AssertExecExecuted

func AssertExecExecuted(t *testing.T, db *TestDB, sqlPattern string)

AssertExecExecuted asserts that an exec matching the SQL pattern was executed on the TestDB.

Example:

db := NewTestDB(dbtypes.PostgreSQL)
// ... execute test code ...
AssertExecExecuted(t, db, "INSERT INTO users")

func AssertExecNotExecuted

func AssertExecNotExecuted(t *testing.T, db *TestDB, sqlPattern string)

AssertExecNotExecuted asserts that no exec matching the SQL pattern was executed on the TestDB.

Example:

db := NewTestDB(dbtypes.PostgreSQL)
// ... execute test code ...
AssertExecNotExecuted(t, db, "DELETE FROM users")

func AssertNoTransaction

func AssertNoTransaction(t *testing.T, db *TestDB)

AssertNoTransaction asserts that no transaction was started on the TestDB.

Example:

db := NewTestDB(dbtypes.PostgreSQL)
// ... execute test code that should NOT use transactions ...
AssertNoTransaction(t, db)

func AssertQueryCount

func AssertQueryCount(t *testing.T, db *TestDB, sqlPattern string, expected int)

AssertQueryCount asserts that exactly N queries matching the SQL pattern were executed.

Example:

db := NewTestDB(dbtypes.PostgreSQL)
// ... execute test code that should call SELECT twice ...
AssertQueryCount(t, db, "SELECT", 2)

func AssertQueryExecuted

func AssertQueryExecuted(t *testing.T, db *TestDB, sqlPattern string)

AssertQueryExecuted asserts that a query matching the SQL pattern was executed on the TestDB. Uses partial matching by default (can be changed with db.StrictSQLMatching()).

Example:

db := NewTestDB(dbtypes.PostgreSQL)
// ... execute test code ...
AssertQueryExecuted(t, db, "SELECT * FROM users")

func AssertQueryNotExecuted

func AssertQueryNotExecuted(t *testing.T, db *TestDB, sqlPattern string)

AssertQueryNotExecuted asserts that no query matching the SQL pattern was executed on the TestDB.

Example:

db := NewTestDB(dbtypes.PostgreSQL)
// ... execute test code ...
AssertQueryNotExecuted(t, db, "DELETE FROM users")

func AssertRolledBack

func AssertRolledBack(t *testing.T, tx *TestTx)

AssertRolledBack asserts that the transaction was rolled back.

Example:

tx := db.ExpectTransaction()
// ... execute test code that should fail and rollback ...
AssertRolledBack(t, tx)

func AssertTransactionCommitted

func AssertTransactionCommitted(t *testing.T, db *TestDB)

AssertTransactionCommitted asserts that the TestDB's transaction was committed. This is a convenience wrapper around AssertCommitted that extracts the transaction from TestDB.

Example:

db := NewTestDB(dbtypes.PostgreSQL)
db.ExpectTransaction()
// ... execute test code ...
AssertTransactionCommitted(t, db)

func AssertTransactionRolledBack

func AssertTransactionRolledBack(t *testing.T, db *TestDB)

AssertTransactionRolledBack asserts that the TestDB's transaction was rolled back. This is a convenience wrapper around AssertRolledBack that extracts the transaction from TestDB.

Example:

db := NewTestDB(dbtypes.PostgreSQL)
db.ExpectTransaction()
// ... execute test code that should fail ...
AssertTransactionRolledBack(t, db)

Types

type ExecCall

type ExecCall struct {
	SQL  string
	Args []any
}

ExecCall represents a single Exec invocation.

type ExecExpectation

type ExecExpectation struct {
	// contains filtered or unexported fields
}

ExecExpectation defines what should happen when Exec is executed.

func (*ExecExpectation) WillReturnError

func (ee *ExecExpectation) WillReturnError(err error) *ExecExpectation

WillReturnError configures the ExecExpectation to return an error. Returns the expectation for method chaining.

func (*ExecExpectation) WillReturnRowsAffected

func (ee *ExecExpectation) WillReturnRowsAffected(n int64) *ExecExpectation

WillReturnRowsAffected configures the ExecExpectation to return the specified rows affected count. Returns the expectation for method chaining.

type QueryCall

type QueryCall struct {
	SQL  string
	Args []any
}

QueryCall represents a single Query or QueryRow invocation.

type QueryExpectation

type QueryExpectation struct {
	// contains filtered or unexported fields
}

QueryExpectation defines what should happen when a query is executed.

func (*QueryExpectation) WillReturnError

func (qe *QueryExpectation) WillReturnError(err error) *QueryExpectation

WillReturnError configures the QueryExpectation to return an error. Returns the expectation for method chaining.

func (*QueryExpectation) WillReturnRows

func (qe *QueryExpectation) WillReturnRows(rows *RowSet) *QueryExpectation

WillReturnRows configures the QueryExpectation to return the specified rows. Returns the expectation for method chaining.

type RowSet

type RowSet struct {
	// contains filtered or unexported fields
}

RowSet represents a collection of rows for testing Query results. It provides a fluent API for building test data that can be returned from TestDB.Query().

RowSet is vendor-agnostic - it works the same way for PostgreSQL, Oracle, and MongoDB tests.

Usage example:

rows := NewRowSet("id", "name", "email").
    AddRow(1, "Alice", "alice@example.com").
    AddRow(2, "Bob", "bob@example.com")

db.ExpectQuery("SELECT").WillReturnRows(rows)

func NewRowSet

func NewRowSet(columns ...string) *RowSet

NewRowSet creates a new RowSet with the specified column names. Column names are informational only (for debugging) and don't affect query execution.

Example:

rs := NewRowSet("id", "name")  // Two columns

func (*RowSet) AddRow

func (rs *RowSet) AddRow(values ...any) *RowSet

AddRow adds a single row of values to the RowSet. The number of values must match the number of columns specified in NewRowSet(). Returns the RowSet for method chaining.

Example:

rs := NewRowSet("id", "name").
    AddRow(1, "Alice").
    AddRow(2, "Bob")

Panics if the number of values doesn't match the number of columns.

func (*RowSet) AddRows

func (rs *RowSet) AddRows(count int, generator func(i int) []any) *RowSet

AddRows adds multiple rows using a generator function. The generator is called count times with index i (0-based). Returns the RowSet for method chaining.

Example (generate 100 test users):

rs := NewRowSet("id", "name").
    AddRows(100, func(i int) []any {
        return []any{int64(i+1), fmt.Sprintf("User%d", i+1)}
    })

func (*RowSet) AddRowsFromStructs

func (rs *RowSet) AddRowsFromStructs(structs ...any) *RowSet

AddRowsFromStructs extracts values from struct instances and adds them as rows. Structs must have `db:"column_name"` tags matching the RowSet columns. Returns the RowSet for method chaining.

Example:

type User struct {
    ID   int64  `db:"id"`
    Name string `db:"name"`
}

rs := NewRowSet("id", "name").
    AddRowsFromStructs(
        &User{ID: 1, Name: "Alice"},
        &User{ID: 2, Name: "Bob"},
    )

This is useful when you have existing struct instances and want to use them as test data.

func (*RowSet) Columns

func (rs *RowSet) Columns() []string

Columns returns the column names for this RowSet.

func (*RowSet) RowCount

func (rs *RowSet) RowCount() int

RowCount returns the number of rows in the RowSet.

type TenantDBMap

type TenantDBMap struct {
	// contains filtered or unexported fields
}

TenantDBMap maps tenant IDs to TestDB instances for multi-tenant testing. It provides a convenient way to test multi-tenant code by setting up different database expectations for each tenant.

Usage example:

tenants := NewTenantDBMap()

// Setup tenant-specific expectations
tenants.ForTenant("acme").
    ExpectQuery("SELECT * FROM products").
        WillReturnRows(NewRowSet("id", "name").AddRow(1, "Acme Widget"))

tenants.ForTenant("globex").
    ExpectQuery("SELECT * FROM products").
        WillReturnRows(NewRowSet("id", "name").AddRow(2, "Globex Gadget"))

// Inject into ModuleDeps
deps := &app.ModuleDeps{
    GetDB: tenants.AsGetDBFunc(),
}

// Test with tenant context
ctx := multitenant.SetTenant(context.Background(), "acme")
products, err := svc.GetProducts(ctx)
// assertions...

func NewTenantDBMap

func NewTenantDBMap() *TenantDBMap

NewTenantDBMap creates a new tenant database map with the default vendor (PostgreSQL).

func NewTenantDBMapWithVendor

func NewTenantDBMapWithVendor(vendor string) *TenantDBMap

NewTenantDBMapWithVendor creates a new tenant database map with the specified default vendor. All TestDB instances created via ForTenant() will use this vendor unless overridden.

func (*TenantDBMap) AllTenantIDs

func (m *TenantDBMap) AllTenantIDs() []string

AllTenantIDs returns all tenant IDs that have configured TestDB instances. Useful for iterating over all tenants in assertions.

Example:

tenants := NewTenantDBMap()
// ... run test code ...
for _, tenantID := range tenants.AllTenantIDs() {
    db := tenants.TenantDB(tenantID)
    AssertQueryExecuted(t, db, "SELECT")
}

func (*TenantDBMap) AsGetDBFunc

func (m *TenantDBMap) AsGetDBFunc() func(context.Context) (dbtypes.Interface, error)

AsGetDBFunc returns a function compatible with ModuleDeps.GetDB. The returned function extracts the tenant ID from context and returns the corresponding TestDB.

Behavior:

  • If tenant ID found in context: Returns TestDB for that tenant (or error if not configured)
  • If no tenant in context and default DB set: Returns default DB
  • If no tenant in context and no default DB: Returns error

Example:

tenants := NewTenantDBMap()
tenants.ForTenant("acme").ExpectQuery("SELECT").WillReturnRows(...)

deps := &app.ModuleDeps{
    GetDB: tenants.AsGetDBFunc(),
}

ctx := multitenant.SetTenant(context.Background(), "acme")
db, err := deps.GetDB(ctx)  // Returns TestDB for "acme"

func (*TenantDBMap) ForTenant

func (m *TenantDBMap) ForTenant(tenantID string) *TestDB

ForTenant returns the TestDB for the specified tenant ID. If no TestDB exists for this tenant, a new one is created with the default vendor. Returns the TestDB for method chaining.

Example:

tenants := NewTenantDBMap()
tenants.ForTenant("acme").ExpectQuery("SELECT").WillReturnRows(...)
tenants.ForTenant("globex").ExpectQuery("SELECT").WillReturnRows(...)

func (*TenantDBMap) ForTenantWithVendor

func (m *TenantDBMap) ForTenantWithVendor(tenantID, vendor string) *TestDB

ForTenantWithVendor returns the TestDB for the specified tenant ID with a specific vendor. Useful when different tenants use different database vendors.

Example:

tenants := NewTenantDBMap()
tenants.ForTenantWithVendor("acme", dbtypes.PostgreSQL).ExpectQuery(...)
tenants.ForTenantWithVendor("globex", dbtypes.Oracle).ExpectQuery(...)

func (*TenantDBMap) Reset

func (m *TenantDBMap) Reset()

Reset clears all configured TestDB instances and the default DB. Useful for test cleanup or when reusing a TenantDBMap across test cases.

Example:

tenants := NewTenantDBMap()
t.Run("test1", func(t *testing.T) {
    tenants.ForTenant("acme").ExpectQuery(...)
    // test...
    tenants.Reset()
})

func (*TenantDBMap) SetDefaultDB

func (m *TenantDBMap) SetDefaultDB(db *TestDB) *TenantDBMap

SetDefaultDB sets a fallback TestDB that will be returned when no tenant is in context or when the tenant ID is not found in the map.

This is useful for testing code that should work in both single-tenant and multi-tenant modes.

Example:

tenants := NewTenantDBMap()
tenants.SetDefaultDB(NewTestDB(dbtypes.PostgreSQL).ExpectQuery(...))

// When no tenant in context, uses default DB
ctx := context.Background()
db, _ := tenants.AsGetDBFunc()(ctx)  // Returns default DB

func (*TenantDBMap) TenantDB added in v0.19.0

func (m *TenantDBMap) TenantDB(tenantID string) *TestDB

TenantDB returns the TestDB for a specific tenant ID without context. Returns nil if the tenant has no configured TestDB.

This is useful for assertions that need to inspect a specific tenant's database calls:

tenants := NewTenantDBMap()
// ... run test code ...
acmeDB := tenants.TenantDB("acme")
AssertQueryExecuted(t, acmeDB, "SELECT * FROM products")

type TestDB

type TestDB struct {
	// contains filtered or unexported fields
}

TestDB is an in-memory fake database that implements both database.Querier and database.Interface. It provides a fluent API for setting up query expectations and tracking calls for assertions.

TestDB supports two SQL matching modes:

  • Partial matching (default): Matches if expected SQL is a substring of actual SQL
  • Strict matching: Requires exact SQL match (enable with StrictSQLMatching())

Usage example:

db := NewTestDB(dbtypes.PostgreSQL).
    ExpectQuery("SELECT * FROM users").
        WillReturnRows(NewRowSet("id", "name").AddRow(1, "Alice")).
    ExpectExec("INSERT INTO users").
        WillReturnRowsAffected(1)

deps := &app.ModuleDeps{
    GetDB: func(ctx context.Context) (database.Interface, error) {
        return db, nil
    },
}

For assertion helpers, see the AssertQueryExecuted, AssertExecExecuted functions.

func NewTestDB

func NewTestDB(vendor string) *TestDB

NewTestDB creates a new in-memory fake database for the specified vendor. The vendor parameter should be one of: dbtypes.PostgreSQL, dbtypes.Oracle, dbtypes.MongoDB.

The returned TestDB implements both database.Querier (for simple mocking) and database.Interface (for full compatibility with framework code).

func (*TestDB) Begin

func (db *TestDB) Begin(_ context.Context) (dbtypes.Tx, error)

Begin implements database.Transactor.Begin.

func (*TestDB) BeginTx

func (db *TestDB) BeginTx(ctx context.Context, _ *sql.TxOptions) (dbtypes.Tx, error)

BeginTx implements database.Transactor.BeginTx.

func (*TestDB) Close

func (db *TestDB) Close() error

Close implements database.Interface.Close.

func (*TestDB) CreateMigrationTable

func (db *TestDB) CreateMigrationTable(_ context.Context) error

CreateMigrationTable implements database.Interface.CreateMigrationTable.

func (*TestDB) DatabaseType

func (db *TestDB) DatabaseType() string

DatabaseType implements database.Querier.DatabaseType.

func (*TestDB) Exec

func (db *TestDB) Exec(_ context.Context, query string, args ...any) (sql.Result, error)

Exec implements database.Querier.Exec.

func (*TestDB) ExecLog added in v0.19.0

func (db *TestDB) ExecLog() []ExecCall

ExecLog returns all Exec calls made to this TestDB. Useful for custom assertions beyond the provided helpers.

func (*TestDB) ExpectExec

func (db *TestDB) ExpectExec(sqlPattern string) *ExecExpectation

ExpectExec sets up an expectation for Exec calls. The sqlPattern parameter is matched against actual executions (partial match by default, exact with StrictSQLMatching).

Returns an ExecExpectation builder for configuring the response.

Example:

db.ExpectExec("INSERT INTO users").
    WillReturnRowsAffected(1)

func (*TestDB) ExpectQuery

func (db *TestDB) ExpectQuery(sqlPattern string) *QueryExpectation

ExpectQuery sets up an expectation for Query or QueryRow calls. The sqlPattern parameter is matched against actual queries (partial match by default, exact with StrictSQLMatching).

Returns a QueryExpectation builder for configuring the response.

Example:

db.ExpectQuery("SELECT * FROM users WHERE id = $1").
    WillReturnRows(NewRowSet("id", "name").AddRow(1, "Alice"))

func (*TestDB) ExpectTransaction

func (db *TestDB) ExpectTransaction() *TestTx

ExpectTransaction sets up an expectation for Begin() calls. Returns a TestTx that can be configured with query/exec expectations.

Example:

tx := db.ExpectTransaction().
    ExpectExec("INSERT INTO orders").WillReturnRowsAffected(1).
    ExpectExec("INSERT INTO items").WillReturnRowsAffected(3)

func (*TestDB) Health

func (db *TestDB) Health(_ context.Context) error

Health implements database.Interface.Health.

func (*TestDB) MigrationTable added in v0.19.0

func (db *TestDB) MigrationTable() string

MigrationTable implements database.Interface.MigrationTable.

func (*TestDB) Prepare

func (db *TestDB) Prepare(_ context.Context, _ string) (dbtypes.Statement, error)

Prepare implements database.Interface.Prepare (rarely used in tests).

func (*TestDB) Query

func (db *TestDB) Query(_ context.Context, query string, args ...any) (*sql.Rows, error)

Query implements database.Querier.Query.

IMPORTANT: Callers MUST call defer rows.Close() immediately after Query() to prevent resource leaks. The returned *sql.Rows is backed by a temporary *sql.DB that requires explicit cleanup.

Correct usage:

rows, err := db.Query(ctx, "SELECT * FROM users")
if err != nil {
    return err
}
defer rows.Close()  // REQUIRED

for rows.Next() {
    // ... scan rows
}

func (*TestDB) QueryLog added in v0.19.0

func (db *TestDB) QueryLog() []QueryCall

QueryLog returns all Query/QueryRow calls made to this TestDB. Useful for custom assertions beyond the provided helpers.

func (*TestDB) QueryRow

func (db *TestDB) QueryRow(_ context.Context, query string, args ...any) dbtypes.Row

QueryRow implements database.Querier.QueryRow.

func (*TestDB) Stats

func (db *TestDB) Stats() (map[string]any, error)

Stats implements database.Interface.Stats.

func (*TestDB) StrictSQLMatching

func (db *TestDB) StrictSQLMatching() *TestDB

StrictSQLMatching enables exact SQL matching instead of partial substring matching. Returns the TestDB for method chaining.

Default behavior (partial matching):

db.ExpectQuery("SELECT")  // Matches "SELECT * FROM users WHERE id = $1"

With strict matching:

db.StrictSQLMatching().ExpectQuery("SELECT * FROM users WHERE id = $1")  // Exact match only

type TestTx

type TestTx struct {
	// contains filtered or unexported fields
}

TestTx is an in-memory fake transaction that implements database.Tx. It tracks query/exec calls within the transaction and records commit/rollback behavior.

TestTx is created via TestDB.ExpectTransaction() and can be configured with expectations for queries and execs that should occur within the transaction.

Usage example:

db := NewTestDB(dbtypes.PostgreSQL)
tx := db.ExpectTransaction().
    ExpectExec("INSERT INTO orders").WillReturnRowsAffected(1).
    ExpectExec("INSERT INTO items").WillReturnRowsAffected(3)

// Test code that uses transactions
svc := NewOrderService(deps)
err := svc.CreateWithItems(ctx, order, items)

// Assert transaction was committed
AssertCommitted(t, tx)

func (*TestTx) Commit

func (tx *TestTx) Commit(_ context.Context) error

Commit implements dbtypes.Tx.Commit.

func (*TestTx) Exec

func (tx *TestTx) Exec(_ context.Context, query string, args ...any) (sql.Result, error)

Exec implements dbtypes.Tx.Exec.

func (*TestTx) ExecLog added in v0.19.0

func (tx *TestTx) ExecLog() []ExecCall

ExecLog returns all Exec calls made within this transaction.

func (*TestTx) ExpectExec

func (tx *TestTx) ExpectExec(sqlPattern string) *TestTx

ExpectExec sets up an expectation for Exec calls within the transaction. Returns the TestTx for method chaining.

Example:

tx := db.ExpectTransaction().
    ExpectExec("INSERT INTO users").WillReturnRowsAffected(1)

func (*TestTx) ExpectQuery

func (tx *TestTx) ExpectQuery(sqlPattern string) *TestTx

ExpectQuery sets up an expectation for Query or QueryRow calls within the transaction. Returns the TestTx for method chaining.

Example:

tx := db.ExpectTransaction().
    ExpectQuery("SELECT * FROM users WHERE id = $1").
        WillReturnRows(NewRowSet("id", "name").AddRow(1, "Alice"))

func (*TestTx) IsCommitted

func (tx *TestTx) IsCommitted() bool

IsCommitted returns true if the transaction was committed.

func (*TestTx) IsRolledBack

func (tx *TestTx) IsRolledBack() bool

IsRolledBack returns true if the transaction was rolled back.

func (*TestTx) Prepare

func (tx *TestTx) Prepare(_ context.Context, _ string) (dbtypes.Statement, error)

Prepare implements dbtypes.Tx.Prepare (rarely used in tests).

func (*TestTx) Query

func (tx *TestTx) Query(_ context.Context, query string, args ...any) (*sql.Rows, error)

Query implements dbtypes.Tx.Query.

IMPORTANT: Callers MUST call defer rows.Close() immediately after Query() to prevent resource leaks. The returned *sql.Rows is backed by a temporary *sql.DB that requires explicit cleanup.

Correct usage:

rows, err := tx.Query(ctx, "SELECT * FROM users")
if err != nil {
    return err
}
defer rows.Close()  // REQUIRED

for rows.Next() {
    // ... scan rows
}

func (*TestTx) QueryLog added in v0.19.0

func (tx *TestTx) QueryLog() []QueryCall

QueryLog returns all Query/QueryRow calls made within this transaction.

func (*TestTx) QueryRow

func (tx *TestTx) QueryRow(_ context.Context, query string, args ...any) dbtypes.Row

QueryRow implements dbtypes.Tx.QueryRow.

func (*TestTx) Rollback

func (tx *TestTx) Rollback(_ context.Context) error

Rollback implements dbtypes.Tx.Rollback.

func (*TestTx) WillReturnRows

func (tx *TestTx) WillReturnRows(rows *RowSet) *TestTx

WillReturnRows configures the last ExpectQuery to return the specified rows. This is a convenience method that operates on the most recently added query expectation. Returns the TestTx for method chaining.

Example:

tx.ExpectQuery("SELECT").WillReturnRows(NewRowSet("id").AddRow(1))

func (*TestTx) WillReturnRowsAffected

func (tx *TestTx) WillReturnRowsAffected(n int64) *TestTx

WillReturnRowsAffected configures the last ExpectExec to return the specified rows affected count. This is a convenience method that operates on the most recently added exec expectation. Returns the TestTx for method chaining.

Example:

tx.ExpectExec("INSERT").WillReturnRowsAffected(5)

type TxExpectation

type TxExpectation struct {
	// contains filtered or unexported fields
}

TxExpectation tracks expected transaction behavior.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL