testenv
A comprehensive Go framework for managing PostgreSQL test environments with container support, connection pooling, and automatic migrations.
Overview
testenv provides a clean, composable system for setting up and managing database environments in tests and production code. It offers flexible connection management with support for Docker containers, connection pooling, multiple reuse strategies, and automatic migration application.
Features
- Environment Management: High-level abstractions for managing database connections
- Container Support: Seamless integration with testcontainers for isolated test environments
- Connection Pooling: Efficient connection management via pgx
- Multiple Reuse Strategies:
- Create new database per connection (full isolation)
- Create new schema per connection (good isolation, better performance)
- Raw connection (no additional isolation)
- Automatic Migrations: Apply migrations automatically when connecting
- Standard Library Compatibility: Works with both pgx and standard
*sql.DB
- Thread Safe: All components are safe for concurrent use
- Test Helpers: Built-in utilities for test cleanup and lifecycle management
Installation
go get go.amidman.dev/testenv
Quick Start
Basic Usage
package main
import (
"context"
"fmt"
"testing"
"go.amidman.dev/testenv/dbenv"
"go.amidman.dev/testenv/postgresenv"
)
func TestWithDatabase(t *testing.T) {
t.Parallel()
// Create a connector
connector := postgresenv.NewConnector(
postgresenv.WithHost("localhost"),
postgresenv.WithPort(5432),
postgresenv.WithDatabase("testdb"),
postgresenv.WithUser("testuser"),
postgresenv.WithPassword("testpass"),
)
// Create environment with reuse strategy
env := postgresenv.New(
postgresenv.WithConnectorReuseOptions(func() (*postgresenv.Connector, error) {
return connector, nil
}),
postgresenv.WithReuseStrategy(&postgresenv.ReuseStrategyCreateNewDB{}),
)
// Connect - automatically creates a new isolated database
pool, err := env.Connect(t.Context())
if err != nil {
t.Fatal(err)
}
defer pool.Close()
// Use the pool for database operations
var count int
err = pool.Pool.QueryRow(t.Context(), "SELECT COUNT(*) FROM users").Scan(&count)
if err != nil {
t.Fatal(err)
}
fmt.Printf("User count: %d\n", count)
}
Using with dbenv.UseForTesting
For simpler test setup with automatic cleanup:
import (
"testing"
"go.amidman.dev/testenv/dbenv"
)
func TestWithDbenvHelper(t *testing.T) {
t.Parallel()
// Create environment
env := createTestEnvironment()
// Get database connection - automatically closed after test
db := dbenv.UseForTesting(t, env)
// Use standard *sql.DB
var count int
err := db.QueryRow("SELECT COUNT(*) FROM users").Scan(&count)
require.NoError(t, err)
assert.Equal(t, 0, count)
}
With Migrations
import (
"testing"
"go.amidman.dev/testenv/dbenv"
"go.amidman.dev/testenv/dbenv/migrations"
)
func TestWithMigrations(t *testing.T) {
t.Parallel()
// Create environment
env := createBaseEnvironment()
// Define migrations as an option
migration := migrations.Queries(
"CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT)",
"CREATE INDEX idx_users_name ON users(name)",
)
// Connect with migrations applied via option
db := dbenv.UseForTesting(t, env, migration)
// Database now has users table
_, err := db.Exec("INSERT INTO users (name) VALUES ($1)", "Alice")
require.NoError(t, err)
}
Standard Library Compatibility
// Create stdlib-compatible environment
stdlibEnv := postgresenv.NewStdlib(env)
// Connect using standard library
db, err := stdlibEnv.Connect(ctx)
if err != nil {
return err
}
defer db.Close()
// Use standard *sql.DB
rows, err := db.QueryContext(ctx, "SELECT * FROM users")
Package Structure
dbenv: Core utilities for using database environments
dbenv/migrations: Migration management utilities
postgresenv: PostgreSQL-specific environment management
reuse: Generic resource reuse mechanism
Reuse Strategies
ReuseStrategyCreateNewDB
Creates a new database for each connection. Provides complete isolation but may be slower due to database creation overhead.
env := postgresenv.New(
postgresenv.WithReuseStrategy(&postgresenv.ReuseStrategyCreateNewDB{
NewDBName: func() string {
return "testdb_" + uuid.New().String()
},
}),
)
ReuseStrategyCreateNewSchema
Creates a new schema in an existing database. Good isolation with better performance.
env := postgresenv.New(
postgresenv.WithReuseStrategy(&postgresenv.ReuseStrategyCreateNewSchema{
DB: "mydb",
NewSchemaName: func() string {
return "testschema_" + uuid.New().String()
},
}),
)
Documentation
For detailed documentation, see the package docs:
License
MIT License. See LICENSE for details.