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 ¶
- func AssertCommitted(t *testing.T, tx *TestTx)
- func AssertExecCount(t *testing.T, db *TestDB, sqlPattern string, expected int)
- func AssertExecExecuted(t *testing.T, db *TestDB, sqlPattern string)
- func AssertExecNotExecuted(t *testing.T, db *TestDB, sqlPattern string)
- func AssertNoTransaction(t *testing.T, db *TestDB)
- func AssertQueryCount(t *testing.T, db *TestDB, sqlPattern string, expected int)
- func AssertQueryExecuted(t *testing.T, db *TestDB, sqlPattern string)
- func AssertQueryNotExecuted(t *testing.T, db *TestDB, sqlPattern string)
- func AssertRolledBack(t *testing.T, tx *TestTx)
- func AssertTransactionCommitted(t *testing.T, db *TestDB)
- func AssertTransactionRolledBack(t *testing.T, db *TestDB)
- type ExecCall
- type ExecExpectation
- type QueryCall
- type QueryExpectation
- type RowSet
- type TenantDBMap
- func (m *TenantDBMap) AllTenantIDs() []string
- func (m *TenantDBMap) AsGetDBFunc() func(context.Context) (dbtypes.Interface, error)
- func (m *TenantDBMap) ForTenant(tenantID string) *TestDB
- func (m *TenantDBMap) ForTenantWithVendor(tenantID, vendor string) *TestDB
- func (m *TenantDBMap) Reset()
- func (m *TenantDBMap) SetDefaultDB(db *TestDB) *TenantDBMap
- func (m *TenantDBMap) TenantDB(tenantID string) *TestDB
- type TestDB
- func (db *TestDB) Begin(_ context.Context) (dbtypes.Tx, error)
- func (db *TestDB) BeginTx(ctx context.Context, _ *sql.TxOptions) (dbtypes.Tx, error)
- func (db *TestDB) Close() error
- func (db *TestDB) CreateMigrationTable(_ context.Context) error
- func (db *TestDB) DatabaseType() string
- func (db *TestDB) Exec(_ context.Context, query string, args ...any) (sql.Result, error)
- func (db *TestDB) ExecLog() []ExecCall
- func (db *TestDB) ExpectExec(sqlPattern string) *ExecExpectation
- func (db *TestDB) ExpectQuery(sqlPattern string) *QueryExpectation
- func (db *TestDB) ExpectTransaction() *TestTx
- func (db *TestDB) Health(_ context.Context) error
- func (db *TestDB) MigrationTable() string
- func (db *TestDB) Prepare(_ context.Context, _ string) (dbtypes.Statement, error)
- func (db *TestDB) Query(_ context.Context, query string, args ...any) (*sql.Rows, error)
- func (db *TestDB) QueryLog() []QueryCall
- func (db *TestDB) QueryRow(_ context.Context, query string, args ...any) dbtypes.Row
- func (db *TestDB) Stats() (map[string]any, error)
- func (db *TestDB) StrictSQLMatching() *TestDB
- type TestTx
- func (tx *TestTx) Commit(_ context.Context) error
- func (tx *TestTx) Exec(_ context.Context, query string, args ...any) (sql.Result, error)
- func (tx *TestTx) ExecLog() []ExecCall
- func (tx *TestTx) ExpectExec(sqlPattern string) *TestTx
- func (tx *TestTx) ExpectQuery(sqlPattern string) *TestTx
- func (tx *TestTx) IsCommitted() bool
- func (tx *TestTx) IsRolledBack() bool
- func (tx *TestTx) Prepare(_ context.Context, _ string) (dbtypes.Statement, error)
- func (tx *TestTx) Query(_ context.Context, query string, args ...any) (*sql.Rows, error)
- func (tx *TestTx) QueryLog() []QueryCall
- func (tx *TestTx) QueryRow(_ context.Context, query string, args ...any) dbtypes.Row
- func (tx *TestTx) Rollback(_ context.Context) error
- func (tx *TestTx) WillReturnRows(rows *RowSet) *TestTx
- func (tx *TestTx) WillReturnRowsAffected(n int64) *TestTx
- type TxExpectation
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AssertCommitted ¶
AssertCommitted asserts that the transaction was committed.
Example:
tx := db.ExpectTransaction() // ... execute test code ... AssertCommitted(t, tx)
func AssertExecCount ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 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 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 ¶
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 ¶
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 ¶
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 ¶
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.
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 ¶
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 ¶
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) CreateMigrationTable ¶
CreateMigrationTable implements database.Interface.CreateMigrationTable.
func (*TestDB) DatabaseType ¶
DatabaseType implements database.Querier.DatabaseType.
func (*TestDB) ExecLog ¶ added in v0.19.0
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 ¶
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) MigrationTable ¶ added in v0.19.0
MigrationTable implements database.Interface.MigrationTable.
func (*TestDB) Query ¶
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
QueryLog returns all Query/QueryRow calls made to this TestDB. Useful for custom assertions beyond the provided helpers.
func (*TestDB) StrictSQLMatching ¶
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) ExecLog ¶ added in v0.19.0
ExecLog returns all Exec calls made within this transaction.
func (*TestTx) ExpectExec ¶
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 ¶
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 ¶
IsCommitted returns true if the transaction was committed.
func (*TestTx) IsRolledBack ¶
IsRolledBack returns true if the transaction was rolled back.
func (*TestTx) Query ¶
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
QueryLog returns all Query/QueryRow calls made within this transaction.
func (*TestTx) WillReturnRows ¶
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 ¶
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.