sqltestutil

package module
v1.0.8 Latest Latest
Warning

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

Go to latest
Published: Mar 1, 2024 License: MIT Imports: 20 Imported by: 0

README

sqltestutil

Documentation

Utilities for testing Golang code that runs SQL.

Quick Start

# Postgres version is "12"
pg, _ := sqltestutil.StartPostgresContainer(context.Background(), "12")
defer pg.Shutdown(ctx)
db, err := sql.Open("postgres", pg.ConnectionString())
// ... execute SQL

Usage

PostgresContainer

PostgresContainer is a Docker container running Postgres that can be used to cheaply start a throwaway Postgres instance for testing.

RunMigration

RunMigration reads all of the files matching *.up.sql in a directory and executes them in lexicographical order against the provided DB.

LoadScenario

LoadScenario reads a YAML "scenario" file and uses it to populate the given DB.

Suite

Suite is a testify suite that provides a database connection for running tests against a SQL database.

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func LoadScenario

func LoadScenario(ctx context.Context, db ExecerContext, filename string) error

LoadScenario reads a YAML "scenario" file and uses it to populate the given db. Top-level keys in the YAML are treated as table names having repeated rows, where keys on each row are column names. For example:

users:
   - id: 1
     name: Alice
     email: alice@example.com
   - id: 2
     name: Bob
     email: bob@example.com

posts:
   - user_id: 1
     title: Hello, world!
   - user_id: 2
     title: Goodbye, world!
     is_draft: true

The above would populate the users and posts tables. Fields that are missing from the YAML are left out of the INSERT statement, and so are populated with the default value for that column.

func RunMigrations

func RunMigrations(ctx context.Context, db ExecerContext, migrationDir string) error

RunMigrations reads all of the files matching *.up.sql in migrationDir and executes them in lexicographical order against the provided db. A typical convention is to use a numeric prefix for each new migration, e.g.:

001_create_users.up.sql
002_create_posts.up.sql
003_create_comments.up.sql

Note that this function does not check whether the migration has already been run. Its primary purpose is to initialize a test database.

Types

type ExecerContext

type ExecerContext interface {
	ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
}

ExecerContext is an interface used by MustExecContext and LoadFileContext

type Option added in v1.0.6

type Option func(*PostgresContainerConfig)

PostgresContainerConfig setter

func WithDBName added in v1.0.6

func WithDBName(dbName string) Option

WithDBName sets the DBName field of the PostgresContainerConfig

func WithDBPassword added in v1.0.6

func WithDBPassword(dbPassword string) Option

WithDBPassword sets the DBPassword field of the PostgresContainerConfig

func WithDBUser added in v1.0.6

func WithDBUser(dbUser string) Option

WithDBUser sets the DBUser field of the PostgresContainerConfig

func WithSSLMode added in v1.0.6

func WithSSLMode(sslMode string) Option

WithSSLMode sets the SSLMode field of the PostgresContainerConfig

func WithTimeZone added in v1.0.6

func WithTimeZone(timeZone string) Option

WithTimeZone sets the TimeZone field of the PostgresContainerConfig

type PostgresContainer

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

PostgresContainer is a Docker container running Postgres. It can be used to cheaply start a throwaway Postgres instance for testing.

Example
ctx := context.Background()

// Create a new container
container, err := StartPostgresContainer(ctx, "15")
if err != nil {
	log.Fatalf("could not start container: %v", err)
}
defer container.Shutdown(ctx)

// Get the connection string
connStr := container.ConnectionString()

db, err := sql.Open("pgx", connStr)
if err != nil {
	container.Shutdown(ctx)
	log.Fatalf("could not open connection: %v", err)
}

if err := db.Ping(); err != nil {
	container.Shutdown(ctx)
	log.Fatalf("could not ping database: %v", err)
}

// Do something with the database
row := db.QueryRowContext(ctx, "SELECT 2024")
var result int
if err := row.Scan(&result); err != nil {
	container.Shutdown(ctx)
	log.Fatalf("could not scan row: %v", err)
}

fmt.Println(result)
Output:

2024

func StartPostgresContainer

func StartPostgresContainer(
	ctx context.Context,
	version string,
	options ...Option,
) (*PostgresContainer, error)

StartPostgresContainer starts a new Postgres Docker container. The version parameter is the tagged version of Postgres image to use, e.g. to use postgres:12 pass "12". Creation involes a few steps:

1. Pull the image if it isn't already cached locally 2. Start the container 3. Wait for Postgres to be healthy

Once created the container will be immediately usable. It should be stopped with the Shutdown method. The container will bind to a randomly available host port, and random password. The SQL connection string can be obtained with the ConnectionString method.

Container startup and shutdown together can take a few seconds (longer when the image has not yet been pulled.) This is generally too slow to initiate in each unit test so it's advisable to do setup and teardown once for a whole suite of tests. TestMain is one way to do this, however because of Golang issue 37206 1, panics in tests will immediately exit the process without giving you the opportunity to Shutdown, which results in orphaned containers lying around.

Another approach is to write a single test that starts and stops the container and then run sub-tests within there. The testify 2 suite package provides a good way to structure these kinds of tests:

type ExampleTestSuite struct {
    suite.Suite
}

func (s *ExampleTestSuite) TestExample() {
    // test something
}

func TestExampleTestSuite(t *testing.T) {
    pg, _ := sqltestutil.StartPostgresContainer(context.Background(), "12")
    defer pg.Shutdown(ctx)
    suite.Run(t, &ExampleTestSuite{})
}

func (*PostgresContainer) ConnectionString

func (c *PostgresContainer) ConnectionString() string

ConnectionString returns a connection URL string that can be used to connect to the running Postgres container.

func (*PostgresContainer) ID

func (c *PostgresContainer) ID() string

ID returns the Docker container ID of the running Postgres container.

func (*PostgresContainer) Shutdown

func (c *PostgresContainer) Shutdown(ctx context.Context) error

Shutdown cleans up the Postgres container by stopping and removing it. This should be called each time a PostgresContainer is created to avoid orphaned containers.

type PostgresContainerConfig added in v1.0.6

type PostgresContainerConfig struct {
	// DBName is to set POSTGRES_DB environment variable
	DBName string
	// DBUser is to set POSTGRES_USER environment variable
	DBUser string
	// DBPassword is to set POSTGRES_PASSWORD environment variable
	DBPassword string
	// TimeZone is to set TZ environment variable. It's also used to set timezone query parameter in the connection string
	TimeZone string
	// SSLMode is to set sslmode query parameter in the connection string
	SSLMode string
}

PostgresContainerConfig is a configuration struct for PostgresContainer. It's used to pass configuration options to the StartPostgresContainer

type Suite

type Suite struct {
	suite.Suite

	// Context is a required field for constructing a Suite, and is used for
	// database operations within a suite. It's public because it's convenient to
	// have access to it in tests.
	context.Context

	// DriverName is a required field for constructing a Suite, and is used to
	// connect to the underlying SQL database.
	DriverName string

	// DataSourceName is a required field for constructing a Suite, and is used to
	// connect to the underlying SQL database.
	DataSourceName string
	// contains filtered or unexported fields
}

Suite is a testify suite 1 that provides a database connection for running tests against a SQL database. For each test that is run, a new transaction is started, and then rolled back at the end of the test so that each test can operate on a clean slate. Here's an example of how to use it:

    type ExampleTestSuite struct {
        sqltestutil.Suite
    }

    func (s *ExampleTestSuite) TestExample() {
        _, err := s.Tx().Exec("INSERT INTO foo (bar) VALUES (?)", "baz")
        s.Assert().NoError(err)
    }

    func TestExampleTestSuite(t *testing.T) {
        suite.Run(t, &ExampleTestSuite{
		       Suite: sqltestutil.Suite{
                Context: context.Background(),
                DriverName: "pgx",
			       DataSourceName: "postgres://localhost:5432/example",
            },
        })
    }

func (*Suite) DB

func (s *Suite) DB() *sql.DB

DB returns the underlying SQL connection.

func (*Suite) SetupSuite

func (s *Suite) SetupSuite()

func (*Suite) TearDownSuite

func (s *Suite) TearDownSuite()

func (*Suite) TearDownTest

func (s *Suite) TearDownTest()

func (*Suite) Tx

func (s *Suite) Tx() *sql.Tx

Tx returns the transaction for the current test.

Jump to

Keyboard shortcuts

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