postgres

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Nov 9, 2025 License: MIT Imports: 16 Imported by: 0

README

go-testcontainers-postgres

PostgreSQL testcontainer utilities for Go tests with PostGIS support, automatic migration detection and running, and cleanup helpers for test isolation.

Features

  • PostgreSQL with PostGIS: Uses postgis/postgis:16-3.4 image for geospatial queries
  • Docker availability checking: Detailed error messages when Docker is unavailable
  • Automatic migration detection: Auto-discovers and runs database migrations
  • Test isolation utilities: CleanAllTables() and CleanSpecificTables() for cleanup
  • Multiple database support: Create isolated databases within the same container
  • Connection pooling: Configurable connection pool settings
  • Enhanced error handling: Specific error types for common failure scenarios
  • Helper functions: Deferred cleanup patterns for easy test setup

Requirements

  • Docker: Docker must be installed and running
  • Go: 1.21 or higher
Installing Docker

macOS:

brew install docker
# Or download Docker Desktop from https://www.docker.com/products/docker-desktop

Linux:

# Ubuntu/Debian
sudo apt-get update
sudo apt-get install docker.io

# Fedora
sudo dnf install docker

# Start Docker service
sudo systemctl start docker
sudo systemctl enable docker

Windows: Download and install Docker Desktop from https://www.docker.com/products/docker-desktop

Installation

go get github.com/JohnPlummer/go-testcontainers-postgres

Quick Start

package mypackage_test

import (
 "context"
 "testing"

 postgres "github.com/JohnPlummer/go-testcontainers-postgres"
)

func TestMyFunction(t *testing.T) {
 ctx := context.Background()

 // Start PostgreSQL container with default settings
 tc, err := postgres.StartSimplePostgreSQLContainer(ctx)
 if err != nil {
  t.Fatalf("Failed to start PostgreSQL container: %v", err)
 }
 defer tc.Close()

 // Use the connection pool
 _, err = tc.Pool.Exec(ctx, `
  CREATE TABLE users (
   id SERIAL PRIMARY KEY,
   name TEXT NOT NULL
  )
 `)
 if err != nil {
  t.Fatalf("Failed to create table: %v", err)
 }

 // Your test logic here
}

Configuration Options

Create a custom configuration:

config := &postgres.PostgreSQLConfig{
 DatabaseName:      "customdb",
 Username:          "customuser",
 Password:          "custompass",
 PostgreSQLVersion: "16-3.4", // PostgreSQL 16 with PostGIS 3.4
 MaxConns:          10,
 MinConns:          2,
 MaxConnLife:       30 * time.Minute,
 MaxConnIdle:       5 * time.Minute,
 StartupTimeout:    30 * time.Second,
 RunMigrations:     true,
 MigrationsPath:    "database/migrations",
}

tc, err := postgres.StartPostgreSQLContainer(ctx, config)
Configuration Fields
Field Type Default Description
DatabaseName string "testdb" Name of the database to create
Username string "testuser" Database username
Password string "testpass" Database password
PostgreSQLVersion string "16-3.4" PostgreSQL-PostGIS version (format: pg_version-postgis_version)
MaxConns int32 10 Maximum connections in pool
MinConns int32 2 Minimum connections in pool
MaxConnLife time.Duration 30m Maximum connection lifetime
MaxConnIdle time.Duration 5m Maximum connection idle time
StartupTimeout time.Duration 30s Container startup timeout
RunMigrations bool false Whether to run migrations on startup
MigrationsPath string "" Path to migrations (auto-detected if empty)

PostGIS Support

The package uses the postgis/postgis image, which includes PostGIS extensions for geospatial queries:

tc, err := postgres.StartSimplePostgreSQLContainer(ctx)
if err != nil {
 t.Fatalf("Failed to start container: %v", err)
}
defer tc.Close()

// Create table with geography column
_, err = tc.Pool.Exec(ctx, `
 CREATE TABLE locations (
  id SERIAL PRIMARY KEY,
  name TEXT,
  location GEOGRAPHY(POINT)
 )
`)

// Insert geospatial data
_, err = tc.Pool.Exec(ctx, `
 INSERT INTO locations (name, location)
 VALUES ('San Francisco', ST_MakePoint(-122.4194, 37.7749))
`)

// Query with spatial functions
rows, err := tc.Pool.Query(ctx, `
 SELECT name
 FROM locations
 WHERE ST_DWithin(
  location,
  ST_MakePoint(-122.4, 37.7)::geography,
  10000  -- 10km radius
 )
`)

Migration Support

Automatic Migration Detection

The package automatically detects migration directories in your project:

tc, err := postgres.StartPostgreSQLContainerWithMigrations(ctx, "")
// Searches for: database/migrations, migrations, sql/migrations, db/migrations
Manual Migration Path

Specify a custom migration path:

config := postgres.DefaultPostgreSQLConfig()
config.RunMigrations = true
config.MigrationsPath = "/path/to/migrations"

tc, err := postgres.StartPostgreSQLContainer(ctx, config)
Environment Variable Override

Set MIGRATIONS_PATH environment variable:

export MIGRATIONS_PATH=/custom/migrations
Migration File Format

Migrations use golang-migrate format:

migrations/
├── 001_create_users.up.sql
├── 001_create_users.down.sql
├── 002_add_posts.up.sql
└── 002_add_posts.down.sql

Example migration:

-- 001_create_users.up.sql
CREATE TABLE users (
 id SERIAL PRIMARY KEY,
 name TEXT NOT NULL,
 email TEXT UNIQUE NOT NULL,
 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_users_email ON users(email);

Test Isolation

Clean All Tables

Remove all data from all tables (except system tables):

func TestWithCleanup(t *testing.T) {
 tc, err := postgres.StartSimplePostgreSQLContainer(ctx)
 if err != nil {
  t.Fatalf("Failed to start container: %v", err)
 }
 defer tc.Close()

 // Setup test data
 setupTestData(tc)

 // Run test
 runTest(tc)

 // Clean all tables for next test
 if err := tc.CleanAllTables(ctx); err != nil {
  t.Fatalf("Failed to clean tables: %v", err)
 }
}
Clean Specific Tables

Remove data from specific tables only:

func TestWithSpecificCleanup(t *testing.T) {
 tc, err := postgres.StartSimplePostgreSQLContainer(ctx)
 if err != nil {
  t.Fatalf("Failed to start container: %v", err)
 }
 defer tc.Close()

 // Only clean specific tables
 if err := tc.CleanSpecificTables(ctx, "users", "posts"); err != nil {
  t.Fatalf("Failed to clean tables: %v", err)
 }
}
Deferred Cleanup Pattern

Use helper functions for automatic cleanup:

func TestWithDeferredCleanup(t *testing.T) {
 tc, err := postgres.StartSimplePostgreSQLContainer(ctx)
 if err != nil {
  t.Fatalf("Failed to start container: %v", err)
 }
 defer tc.WithCleanup()()  // Closes container

 // Or clean specific tables
 defer tc.WithTableCleanup("users", "posts")()

 // Your test logic here
}

Multiple Databases

Create multiple isolated databases within the same container:

tc, err := postgres.StartSimplePostgreSQLContainer(ctx)
if err != nil {
 t.Fatalf("Failed to start container: %v", err)
}
defer tc.Close()

// Create additional databases
db1URL, err := tc.NewTestDatabase("test_db1")
if err != nil {
 t.Fatalf("Failed to create database: %v", err)
}

db2URL, err := tc.NewTestDatabase("test_db2")
if err != nil {
 t.Fatalf("Failed to create database: %v", err)
}

// Each database is completely isolated

Docker Availability Checking

Skip Tests When Docker Unavailable
func TestRequiresDocker(t *testing.T) {
 if shouldSkip, msg := postgres.SkipIfDockerUnavailable(); shouldSkip {
  t.Skip(msg)
 }

 // Test logic that requires Docker
}
Check Docker Status
result := postgres.CheckDockerAvailability()
if !result.Available {
 log.Printf("Docker unavailable: %s", result.Reason)
 if result.Error != nil {
  log.Printf("Error: %v", result.Error)
 }
}

Error Handling

The package provides specific error types for common scenarios:

tc, err := postgres.StartPostgreSQLContainerWithCheck(ctx, config)
if err != nil {
 switch {
 case errors.Is(err, postgres.ErrDockerNotAvailable):
  // Docker is not installed or not running
 case errors.Is(err, postgres.ErrContainerStartTimeout):
  // Container took too long to start
 case errors.Is(err, postgres.ErrContainerPortConflict):
  // Port is already in use
 case errors.Is(err, postgres.ErrDatabaseConnFailed):
  // Could not connect to database
 case errors.Is(err, postgres.ErrMigrationsFailed):
  // Database migrations failed
 default:
  // Other error
 }
}

Troubleshooting

Docker Not Available

Error: Docker is not available or running

Solutions:

  1. Install Docker (see Installation section)
  2. Start Docker daemon:
    • macOS: Open Docker Desktop
    • Linux: sudo systemctl start docker
    • Windows: Start Docker Desktop
  3. Check Docker is running: docker info
Container Startup Timeout

Error: container failed to start within timeout period

Solutions:

  1. Increase startup timeout:

    config := postgres.DefaultPostgreSQLConfig()
    config.StartupTimeout = 60 * time.Second
    
  2. Check Docker resources (CPU, memory)

  3. Pull image manually: docker pull postgis/postgis:16-3.4

Port Conflict

Error: container port conflict detected

Solutions:

  1. Stop conflicting containers: docker ps then docker stop <container_id>
  2. Use dynamic port allocation (default behavior)
  3. Check for other PostgreSQL instances
Migration Failures

Error: database migrations failed

Solutions:

  1. Verify migration files exist and are readable
  2. Check migration SQL syntax
  3. Set MIGRATIONS_PATH environment variable explicitly
  4. Use absolute paths for migration directories
Image Pull Failures

Error: failed to start PostgreSQL container

Solutions:

  1. Check internet connectivity
  2. Pull image manually: docker pull postgis/postgis:16-3.4
  3. Check Docker Hub status
  4. Use a different version if necessary

Best Practices

1. Use Deferred Cleanup

Always defer cleanup to ensure containers are properly terminated:

tc, err := postgres.StartSimplePostgreSQLContainer(ctx)
if err != nil {
 t.Fatalf("Failed to start container: %v", err)
}
defer tc.Close()  // Always defer cleanup
2. Isolate Tests

Clean tables between tests for isolation:

func TestSuite(t *testing.T) {
 tc, err := postgres.StartSimplePostgreSQLContainer(ctx)
 if err != nil {
  t.Fatalf("Failed to start container: %v", err)
 }
 defer tc.Close()

 t.Run("Test1", func(t *testing.T) {
  defer tc.CleanAllTables(ctx)
  // Test logic
 })

 t.Run("Test2", func(t *testing.T) {
  defer tc.CleanAllTables(ctx)
  // Test logic
 })
}
3. Reuse Containers in Test Suites

Start one container for the entire test suite:

var testContainer *postgres.PostgreSQLTestContainer

func TestMain(m *testing.M) {
 ctx := context.Background()

 // Skip tests if Docker unavailable
 if shouldSkip, msg := postgres.SkipIfDockerUnavailable(); shouldSkip {
  fmt.Println(msg)
  os.Exit(0)
 }

 // Setup
 var err error
 testContainer, err = postgres.StartSimplePostgreSQLContainer(ctx)
 if err != nil {
  log.Fatalf("Failed to start container: %v", err)
 }

 // Run tests
 code := m.Run()

 // Teardown
 testContainer.Close()
 os.Exit(code)
}
4. Use Specific Cleanup When Possible

Clean only the tables you need:

// Instead of cleaning all tables
tc.CleanAllTables(ctx)

// Clean only what you need
tc.CleanSpecificTables(ctx, "users", "posts")
5. Handle Docker Unavailable Gracefully
func TestWithDocker(t *testing.T) {
 if shouldSkip, msg := postgres.SkipIfDockerUnavailable(); shouldSkip {
  t.Skip(msg)
 }

 // Test logic
}

Examples

See the examples/ directory for complete working examples:

  • examples/basic/ - Basic container usage
  • examples/migrations/ - Migration support
  • examples/cleanup/ - Table cleanup patterns

Run examples:

cd examples/basic && go run main.go
cd examples/migrations && go run main.go
cd examples/cleanup && go run main.go

API Reference

Functions
  • DefaultPostgreSQLConfig() *PostgreSQLConfig - Returns default configuration
  • CheckDockerAvailability() DockerAvailabilityResult - Checks Docker status
  • StartPostgreSQLContainer(ctx, config) (*PostgreSQLTestContainer, error) - Starts container
  • StartPostgreSQLContainerWithCheck(ctx, config) (*PostgreSQLTestContainer, error) - Starts with Docker check
  • StartSimplePostgreSQLContainer(ctx) (*PostgreSQLTestContainer, error) - Starts with defaults
  • StartPostgreSQLContainerWithMigrations(ctx, path) (*PostgreSQLTestContainer, error) - Starts with migrations
  • SkipIfDockerUnavailable() (bool, string) - Helper for test skipping
  • FindMigrationsPath() string - Auto-detects migration directory
Methods
  • tc.Close() error - Terminates container and closes pool
  • tc.CleanAllTables(ctx) error - Truncates all tables
  • tc.CleanSpecificTables(ctx, tables...) error - Truncates specific tables
  • tc.GetConnectionString() string - Returns database URL
  • tc.GetPool() *pgxpool.Pool - Returns connection pool
  • tc.GetContainer() *postgres.PostgresContainer - Returns container
  • tc.NewTestDatabase(name) (string, error) - Creates new database
  • tc.WithCleanup() func() - Returns cleanup function
  • tc.WithTableCleanup(tables...) func() - Returns table cleanup function

License

MIT License - see LICENSE file for details

Contributing

Contributions welcome! Please open an issue or pull request on GitHub.

Repository

https://github.com/JohnPlummer/go-testcontainers-postgres

Documentation

Overview

Package postgres provides PostgreSQL testcontainer utilities for Go tests.

This package offers utilities for starting PostgreSQL containers in tests, with support for PostGIS, automatic migration detection and running, and cleanup helpers for test isolation.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrDockerNotAvailable    = errors.New("Docker is not available or running")
	ErrContainerStartTimeout = errors.New("container failed to start within timeout period")
	ErrContainerPortConflict = errors.New("container port conflict detected")
	ErrDatabaseConnFailed    = errors.New("failed to connect to container database")
	ErrMigrationsFailed      = errors.New("database migrations failed")
)

Error types for better error handling

Functions

func FindMigrationsPath

func FindMigrationsPath() string

FindMigrationsPath attempts to find the migrations directory This looks for common migration paths relative to the project root.

First checks for MIGRATIONS_PATH environment variable, then falls back to walking up the directory tree from the caller's location to find the project root (marked by .git directory), then searches for common migration paths: database/migrations, migrations, sql/migrations, db/migrations.

Uses .git instead of go.mod for monorepo awareness (multiple go.mod files exist).

Returns "database/migrations" as fallback if no migrations directory found.

func SkipIfDockerUnavailable

func SkipIfDockerUnavailable() (shouldSkip bool, skipMessage string)

SkipIfDockerUnavailable checks Docker availability and returns a skip message if unavailable This is useful for test suites that should gracefully skip when Docker is not available

Types

type DockerAvailabilityResult

type DockerAvailabilityResult struct {
	Available bool
	Reason    string
	Error     error
}

DockerAvailabilityResult holds information about Docker availability

func CheckDockerAvailability

func CheckDockerAvailability() DockerAvailabilityResult

CheckDockerAvailability checks if Docker is available and running

type PostgreSQLConfig

type PostgreSQLConfig struct {
	// Database configuration
	DatabaseName string
	Username     string
	Password     string

	// Image configuration
	PostgreSQLVersion string // e.g., "16-3.4", "15-3.4" (version-postgis_version)

	// Connection configuration
	MaxConns    int32
	MinConns    int32
	MaxConnLife time.Duration
	MaxConnIdle time.Duration

	// Container configuration
	StartupTimeout time.Duration

	// Migration configuration
	RunMigrations  bool
	MigrationsPath string // Relative to the calling test file or absolute path
}

PostgreSQLConfig provides configuration options for the PostgreSQL test container

func DefaultPostgreSQLConfig

func DefaultPostgreSQLConfig() *PostgreSQLConfig

DefaultPostgreSQLConfig returns a sensible default configuration

type PostgreSQLTestContainer

type PostgreSQLTestContainer struct {
	Container    *postgres.PostgresContainer
	Pool         *pgxpool.Pool
	DatabaseURL  string
	Context      context.Context
	DatabaseName string
	Username     string
	Password     string
}

PostgreSQLTestContainer holds the PostgreSQL test container and related resources

func StartPostgreSQLContainer

func StartPostgreSQLContainer(ctx context.Context, config *PostgreSQLConfig) (*PostgreSQLTestContainer, error)

StartPostgreSQLContainer creates and starts a PostgreSQL test container

func StartPostgreSQLContainerWithCheck

func StartPostgreSQLContainerWithCheck(ctx context.Context, config *PostgreSQLConfig) (*PostgreSQLTestContainer, error)

StartPostgreSQLContainerWithCheck creates and starts a PostgreSQL test container with Docker availability checks

func StartPostgreSQLContainerWithMigrations

func StartPostgreSQLContainerWithMigrations(ctx context.Context, migrationsPath string) (*PostgreSQLTestContainer, error)

StartPostgreSQLContainerWithMigrations creates a PostgreSQL container and runs migrations with Docker check

func StartSimplePostgreSQLContainer

func StartSimplePostgreSQLContainer(ctx context.Context) (*PostgreSQLTestContainer, error)

StartSimplePostgreSQLContainer creates a PostgreSQL container with default settings This is a convenience function for simple test setups with Docker availability check

func (*PostgreSQLTestContainer) CleanAllTables

func (tc *PostgreSQLTestContainer) CleanAllTables(ctx context.Context) error

CleanAllTables truncates all tables in the database for test isolation WARNING: This removes ALL data from ALL tables

func (*PostgreSQLTestContainer) CleanSpecificTables

func (tc *PostgreSQLTestContainer) CleanSpecificTables(ctx context.Context, tableNames ...string) error

CleanSpecificTables truncates specific tables for test isolation Only truncates tables that actually exist to avoid errors

func (*PostgreSQLTestContainer) Close

func (tc *PostgreSQLTestContainer) Close() error

Close closes the connection pool and terminates the container

func (*PostgreSQLTestContainer) GetConnectionString

func (tc *PostgreSQLTestContainer) GetConnectionString() string

GetConnectionString returns the database connection string

func (*PostgreSQLTestContainer) GetContainer

GetContainer returns the testcontainers instance

func (*PostgreSQLTestContainer) GetPool

func (tc *PostgreSQLTestContainer) GetPool() *pgxpool.Pool

GetPool returns the connection pool

func (*PostgreSQLTestContainer) NewTestDatabase

func (tc *PostgreSQLTestContainer) NewTestDatabase(dbName string) (string, error)

NewTestDatabase creates a new database within the container for isolation This is useful when you need multiple isolated databases in the same container

func (*PostgreSQLTestContainer) WithCleanup

func (tc *PostgreSQLTestContainer) WithCleanup() func()

WithCleanup returns a cleanup function that can be deferred

func (*PostgreSQLTestContainer) WithTableCleanup

func (tc *PostgreSQLTestContainer) WithTableCleanup(tables ...string) func()

WithTableCleanup returns a cleanup function that truncates specific tables

Directories

Path Synopsis
examples
basic command
cleanup command
migrations command

Jump to

Keyboard shortcuts

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