testenv

module
v0.2.3 Latest Latest
Warning

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

Go to latest
Published: Jul 5, 2026 License: MIT

README

testenv

Coverage

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.WithConnector(connector),
        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
  • minioenv: MinIO environment management (object storage)
  • minioenv/containers: Docker container management for MinIO
  • 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()
        },
    }),
)

minioenv

The minioenv package provides MinIO (S3-compatible object storage) environment management. It mirrors the postgresenv design: Connector owns a reuse instance, Environment applies a strategy, and containers.Container provides a testcontainers-backed lifecycle.

The *minio.Client from github.com/minio/minio-go/v7 is concurrency-safe and is sufficient for both admin (MakeBucket / RemoveBucket) and app operations — no service-pool cache is needed.

Quick Start
Raw client (default)

With no reuse strategy, the environment returns a raw *minio.Client and the user manages buckets themselves.

package main

import (
    "context"
    "fmt"
    "testing"
    
    "go.amidman.dev/testenv/minioenv"
    "go.amidman.dev/testenv/minioenv/containers"
)

func TestWithMinio(t *testing.T) {
    t.Parallel()
    
    // Create a MinIO container and connector
    cnt := containers.NewContainer()
    connector := minioenv.NewConnector(cnt.ConnectorOptions()...)
    
    // Create environment with default (raw) mode
    env := minioenv.New(minioenv.WithConnector(connector))
    
    // Connect - returns a raw *minio.Client via Client
    client, err := env.Connect(t.Context())
    if err != nil {
        t.Fatal(err)
    }
    defer client.Close()
    
    // Use the underlying *minio.Client for object operations
    exists, err := client.BucketExists(t.Context(), "my-bucket")
    if err != nil {
        t.Fatal(err)
    }
    fmt.Printf("Bucket exists: %v\n", exists)
}
WithBuckets — shared, well-known buckets

WithBuckets is an EnvironmentOption that ensures the listed buckets exist before Connect returns. Buckets are not removed on Client.Close — they live for the entire env lifecycle and persist across tests/runs. Use this for shared, well-known buckets.

env := minioenv.New(
    minioenv.WithConnector(connector),
    minioenv.WithBuckets("config", "uploads"),
)

client, _ := env.Connect(ctx)
defer client.Close()

// "config" and "uploads" are guaranteed to exist
_ = client.PutObject(ctx, "config", "app.yaml", reader)
ReuseStrategyCreateNewBucket — per-Connect isolated bucket

For per-Connect isolated buckets with automatic cleanup, use ReuseStrategyCreateNewBucket. The actual bucket name is exposed via Client.Bucket() because MinIO has no equivalent of SQL search_path — the user must name the bucket explicitly when calling PutObject / GetObject / etc.

env := minioenv.New(
    minioenv.WithConnector(connector),
    minioenv.WithReuseStrategy(&minioenv.ReuseStrategyCreateNewBucket{}),
)

client, _ := env.Connect(ctx)
defer client.Close() // bucket is removed on Close

// Use the actual bucket name via Client.Bucket()
err := client.PutObject(ctx, client.Bucket(), "alice.json", reader)
UseForTesting

For tests, UseForTesting mirrors dbenv.UseForTesting: it calls t.Helper(), calls t.Fatal on connect error, and registers a t.Cleanup to call client.Close().

func TestWithMinioForTesting(t *testing.T) {
    t.Parallel()
    
    cnt := containers.NewContainer()
    connector := minioenv.NewConnector(cnt.ConnectorOptions()...)
    env := minioenv.New(
        minioenv.WithConnector(connector),
        minioenv.WithReuseStrategy(&minioenv.ReuseStrategyCreateNewBucket{}),
    )
    
    // Get client — automatically closed after test
    client := minioenv.UseForTesting(t, env)
    
    // Use client in test
    err := client.PutObject(ctx, client.Bucket(), "key", reader)
    require.NoError(t, err)
}
Container

The minioenv/containers subpackage provides a testcontainers-backed Container that manages a real MinIO Docker instance. The container is started lazily on the first Client call (via BeforeConnectHook) and stopped on connector.Close() (via CloseHook).

cnt := containers.NewContainer()
connector := minioenv.NewConnector(cnt.ConnectorOptions()...)
defer connector.Close()

// Container starts automatically on first Client call
client, err := connector.Client(ctx)

// Container stops on Close (via closeHook)
connector.Close()

The default image is minio/minio:RELEASE.2024-01-16T16-07-38Z, matching the tag pinned in testcontainers-go/modules/minio@v0.40.0. Override with containers.WithImage("minio/minio:latest") if needed.

Reuse Strategy
ReuseStrategyCreateNewBucket

Creates a new bucket per connection, removes it on Client.Close(). Provides complete isolation between tests. The actual bucket name is exposed via Client.Bucket().

env := minioenv.New(
    minioenv.WithConnector(connector),
    minioenv.WithReuseStrategy(&minioenv.ReuseStrategyCreateNewBucket{
        NewBucketName: func() string {
            return "my-bucket-" + uuid.New().String()
        },
    }),
)

Documentation

For detailed documentation, see the package docs:

License

MIT License. See LICENSE for details.

Directories

Path Synopsis
migrations
Package migrations provides utilities for managing database migrations in test environments.
Package migrations provides utilities for managing database migrations in test environments.
migrations/goose
Package goosemigrations provides a goose-based migration adapter for the migrations package.
Package goosemigrations provides a goose-based migration adapter for the migrations package.
Package minioenv provides MinIO environment management for testing.
Package minioenv provides MinIO environment management for testing.
containers
Package containers provides Docker container management for MinIO test environments.
Package containers provides Docker container management for MinIO test environments.
Package postgresenv provides PostgreSQL environment management for testing.
Package postgresenv provides PostgreSQL environment management for testing.
containers
Package containers provides Docker container management for PostgreSQL test environments.
Package containers provides Docker container management for PostgreSQL test environments.
Package reuse provides a generic resource reuse mechanism with automatic lifecycle management.
Package reuse provides a generic resource reuse mechanism with automatic lifecycle management.

Jump to

Keyboard shortcuts

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