baloon

package module
v2.0.0+incompatible Latest Latest
Warning

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

Go to latest
Published: Nov 23, 2017 License: MIT Imports: 11 Imported by: 0

README

GoDoc Go Report Card GitHub release

baloon

Baloon is a Setup and Teardown test fixture library for end-to-end testing of HTTP APIs written in Go.

Baloon will setup a database with your sample data, build and run your Go executable, run your tests, and teardown your database afterwards. It also supports setup and teardown routines per unit test.

Baloon is designed to be used in conjunction with an API testing library such baloo (which inspired me to write this test fixture library, hence the name baloon). The goal is to make HTTP API testing less brittle by providing clean and repeatable setup/teardown processes for your main external dependencies, namely databases and compiling/running your program.

Installation

go get gopkg.in/sironfoot/baloon.v2

Or using govendor:

govendor fetch github.com/sironfoot/baloon@v2

Requirements

  • Go 1.7+
  • An end-to-end HTTP API testing library (such as baloo)

Setup

Baloon needs to be run in your Go test's TestMain function. TestMain is a special test function in Go that will run instead of your tests, providing you with the opportunity to run setup and teardown code before running your tests.

func TestMain(m *testing.M) {
	// insert setup code here

	// run all our tests
	code := m.Run()

	// insert teardown code here

	// exit
	os.Exit(code)
}

In this example, we're assuming we have our tests inside a /tests directory at the root of our Go HTTP app. Create a main_test.go file in this dir, this is where we put our TestMain function that includes all our setup code. All code listed below goes in this function.

1. App Root

Lets start by getting an absolute path to your Go app's root directory (containing your main.go):

import "path/filepath"

func TestMain(m *testing.M) {
	appRootPath, err := filepath.Abs("./../")
}
2. Database Setup

Now we setup our fixture. Create one or more Database setup routines.

databaseSetups := []baloon.DB{
	// create the initial database
	baloon.DB{
		Connection: baloon.DBConn{
			Driver: "postgres",
			String: "postgres://user:pw@localhost:5432/?sslmode=disable",
		},
		Scripts: []baloon.Script{
			baloon.NewScript("CREATE DATABASE northwind;"),
		},
	},
	// setup tables, stored procedures etc.
	baloon.DB{
		Connection: baloon.DBConn{
			Driver: "postgres",
			String: "postgres://user:pw@localhost:5432/northwind?sslmode=disable",
		},
		Scripts: []baloon.Script{
			baloon.NewScriptPath("./sql/create tables.sql"),
			baloon.NewScriptPath("./sql/create functions.sql"),
			baloon.NewScriptPath("./sql/create sprocs.sql"),
		},
	},
}

We have 2 setups here because we need to connect the the database server instance (sans any particular database) to first create a database, then a second setup connects to our newly created database to add the tables, sprocs etc.

Baloon uses the "database/sql" package, so will support any database that supports that, but make sure your database driver is imported:

import _ "github.com/lib/pq"

Scripts can be literal scripts (CREATE DATABASE northwind;), or paths to files containing scripts (./sql/create tables.sql). Paths are relative to your app root (see 1. App Root above). Paths support globbing patterns (e.g. ./sql/*.sql).

3. App Executable Setup

Here we provide instructions on how to run our Go HTTP API executable.

appSetup := baloon.App{
	RunArguments: []string{
		"-port", "8080",
		"-db_name", "northwind",
		"-db_port", "5432",
		"-ready_statement", "Test App is Ready",
	},
	WaitForOutputLine: "Test App is Ready",
	WaitTimeout:       5 * time.Second,
}

Baloon will automatically compile our app into the root dir (with a random filename) using go build -o "./filename". It will run our app with the arguments provided, and delete our app executable afterwards.

WaitForOutputLine tells Baloon to wait for a line of text to appear in the stdout or stderr to signal that our app is ready to start accepting HTTP requests. So configure your app to output an appropriate line, or use the standard Listening and serving HTTP on :8080 message that most Go HTTP Web frameworks output. If our app takes a few seconds to startup & initialise, we don't want tests executing against our app before it's ready.

4. Database Teardown

Same as setup but runs after all our tests have finished. Here we just delete our database.

databaseTeardowns := []baloon.DB{
	baloon.DB{
		Connection: baloon.DBConn{
			Driver: "postgres",
			String: "postgres://user:pw@localhost:5432/?sslmode=disable",
		},
		Scripts: []baloon.Script{
			baloon.NewScript("DROP DATABASE IF EXISTS northwind;"),
		},
	},
}
5. Putting It All Together

Make sure our fixture struct is declared as a package level variable, because we'll need it later.

var fixture baloon.Fixture

func TestMain(m *testing.M) {
	// code from above goes here

	setup := baloon.FixtureConfig{
		AppRoot: appRoot,
		DatabaseSetups: databaseSetups,
		AppSetup: appSetup,
		DatabaseTeardowns: databaseTeardowns,
	}

	fixture, err = baloon.NewFixture(setup)
	if err != nil {
		log.Panic(err)
	}
	defer fixture.Close()

	err = fixture.Setup()
	if err != nil {
		log.Panic(err)
	}

	code := m.Run()

	err = fixture.Teardown()
	if err != nil {
		log.Panic(err)
	}

	os.Exit(code)
}
6. Per Unit Test Setup and Teardown

We can run setup and teardown routines per individual unit test. A use case is to add sample data to our database to test against, but have that data reset after each test, as some tests might insert or delete data.

We can also run bespoke code during unit test setup and teardown. For instance, getting an example admin and non-admin user ID if our primary key IDs are auto-generated, and therefore will be different after each setup.

In the TestMain func after creating a new Fixture:

var adminUserID string
var stdUserID string

func TestMain(m *testing.M) {
	// setup code here (snip...)

	fixture, err = baloon.NewFixture(setup)
	if err != nil {
		log.Panic(err)
	}
	defer fixture.Close()

	fixture.AddUnitTestSetup(baloon.UnitTest{
		DatabaseRoutines: []baloon.DB{
			baloon.DB{
				Connection: baloon.DBConn{
					Driver: "postgres",
					String: "postgres://user:pw@localhost:5432/northwind?sslmode=disable",
				},
				Scripts: []baloon.Script{
					baloon.NewScriptPath("./tests/testData/*.sql"),
				},
			},
		},
		Func: func(t *testing.T) {
			adminUserID, err = getAdminUserID("admin@example.com")
			if err != nil {
				t.Fatal(err)
			}

			stdUserID, err = getStandardUserID("user@example.com")
			if err != nil {
				t.Fatal(err)
			}
		},
	})

	fixture.AddUnitTestTeardown(baloon.UnitTest{
		DatabaseRoutines: []baloon.DB{
			baloon.DB{
				Connection: baloon.DBConn{
					Driver: "postgres",
					String: "postgres://user:pw@localhost:5432/northwind?sslmode=disable",
				},
				Scripts: []baloon.Script{
					baloon.NewScript("DELETE FROM orders;"),
					baloon.NewScript("DELETE FROM customers;"),
					baloon.NewScript("DELETE FROM products;"),
				},
			},
		},
		Func: func(t *testing.T) { },
	})

	// fixture setup/teardown, run tests etc. (snip...)
}

Then use these in each unit test:

func TestCustomers_List(t *testing.T) {
	fixture.UnitTestSetup(t)
	defer fixture.UnitTestTeardown(t)

	// test code Here
}

Note: Any database-routine failures during unit test setup/teardowns will result in T.Fatal() being called via the testing.T struct passed in to UnitTestSetup and UnitTestTeardown methods.

For errors in your own bespoke code, you can decide what to do yourself using the testing.T struct passed in.

Tips

Dropping Database Connections

DROP DATABASE commands can fail if there are open/active connections to the database. To make setup and teardown more reliable, it's a good idea to drop these connections as well. Below is an example for postgres:

sqlDropConnections :=
	`SELECT pg_terminate_backend(pg_stat_activity.pid)
	 FROM pg_stat_activity
	 WHERE  pg_stat_activity.datname = 'northwind'
	 	AND pid <> pg_backend_pid();`

databaseTeardowns := []baloon.DB{
	baloon.DB{
		Connection: baloon.DBConn{
			Driver: "postgres",
			String: "postgres://user:pw@localhost:5432/?sslmode=disable",
		},
		Scripts: []baloon.Script{
			baloon.NewScript(sqlDropConnections),
			baloon.NewScript("DROP DATABASE IF EXISTS northwind;"),
		},
	},
}
Drop Database During Setup

Further to the above, it's advisable to attempt to drop any databases during setup as well as teardown. The reasoning is that if our setup/teardown routines fail, we might be left with the Test database still alive, causing database setup routines to fail trying to create a database that already exists.

databaseSetups := []baloon.DB{
	// create the initial database
	baloon.DB{
		Connection: baloon.DBConn{
			Driver: "postgres",
			String: "postgres://user:pw@localhost:5432/?sslmode=disable",
		},
		Scripts: []baloon.Script{
			baloon.NewScript(sqlDropConnections),
			baloon.NewScript("DROP DATABASE IF EXISTS northwind;"),
			baloon.NewScript("CREATE DATABASE northwind;"),
		},
	},
	// setup tables, stored procedures etc.
	// ...snip
}
Custom Setup and Teardown Code

If you want to run any custom setup and teardown code, simply add it to your TestMain() func.

func TestMain(m *testing.M) {
	// fixture setup config (snip...)
	fixture, err = baloon.NewFixture(setup)

	// custom setup code goes here..
	err = fixture.Setup()
	// ...or here

	code := m.Run()

	// custom teardown code goes here...
	err = fixture.Teardown()
	// ...or here

	os.Exit(code)
}
Can I Have My Own Build Arguments?

Yes. Simply use the BuildArguments property when defining the App Executable Setup:

appSetup := baloon.App{
	BuildArguments: []string{
		"-o", "./my_rest_app",
	},
	RunArguments: []string{
		"-ready_statement", "Test App is Ready",
	},
	WaitForOutputLine: "Test App is Ready",
	WaitTimeout:       5 * time.Second,
}

Here we are setting the Go build output -o flag to be ./my_rest_app rather than use a randomly generated file name. Baloon will still delete this executable during Teardown.

Licence

MIT - Dominic Pettifer

Documentation

Overview

Package baloon is a setup and teardown test fixture library for end-to-end testing of HTTP APIs written in Go.

Index

Constants

View Source
const (
	// ScriptTypeLiteral specifies literal database command text
	ScriptTypeLiteral = 1

	// ScriptTypePath specifies a glob file pattern for
	// database commands stored in files
	ScriptTypePath = 2
)

These consts represent the 2 types of database scripts we can use, either literal or file path

Variables

This section is empty.

Functions

This section is empty.

Types

type App

type App struct {
	// BuildArguments is a list of build arguments to include when baloon
	// tries to buld your App executable. They are run via "go build yourArgsHere..."
	BuildArguments []string

	// RunArguments is a list of command line arguments to
	// include when your Go executable is run.
	RunArguments []string

	// WaitForOutputLine specifies a line of text that baloon should wait
	// to appear in either stdout or stderr in order to signal that the App is
	// ready to start excepting HTTP requests.
	WaitForOutputLine string

	// WaitTimeout is how long baloon should wait for the
	// 'WaitForOutputLine' to appear.
	WaitTimeout time.Duration
}

App represents settings and arguments for your Go HTTP API executable.

type DB

type DB struct {
	// Connection stores database connection details including driver and connection
	// string. Uses sql.DB so make sure your driver is imported correctly.
	Connection DBConn

	// Script represents a Script to run on your database
	Script Script

	// Scripts represents multiple scripts to run on your database
	Scripts []Script
}

DB represents a series of database scripts to run against a database given its Connection. It uses database/sql behind the scenes so your database driver will need to support it.

type DBConn

type DBConn struct {
	// Driver is your database driver name, passed as the first
	// argument to sql.Open
	Driver string

	// String is the database connection string, passed as the
	// second argument to sql.Open
	String string
}

DBConn represents a database connection including the driver and connection string. Uses sql.DB, so make sure your database driver package supports it and is imported.

type Fixture

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

Fixture represents a test fixture. You usually have one per test suite.

func NewFixture

func NewFixture(config FixtureConfig) (Fixture, error)

NewFixture returns a Fixture, but also verifies that everything has been set up correctly.

func (*Fixture) AddUnitTestSetup

func (fixture *Fixture) AddUnitTestSetup(setup UnitTest)

AddUnitTestSetup adds a UnitTest setup routine to the test Fixture

func (*Fixture) AddUnitTestTeardown

func (fixture *Fixture) AddUnitTestTeardown(teardown UnitTest)

AddUnitTestTeardown adds a UnitTest teardown routine to the test Fixture

func (*Fixture) Close

func (fixture *Fixture) Close()

Close will attempt to free up any resources created by the Fixture. Make sure to call this before any log.Fatal() or os.Exit() calls.

func (*Fixture) Setup

func (fixture *Fixture) Setup() error

Setup runs the fixture setup. Call this only once before running all your tests, usually in func MainTest()

func (*Fixture) Teardown

func (fixture *Fixture) Teardown() error

Teardown runs the fixture teardown routines. Call this only once after running all your tests, usually in func MainTest() after the call to m.Run()

func (*Fixture) UnitTestSetup

func (fixture *Fixture) UnitTestSetup(t *testing.T)

UnitTestSetup will run all UnitTest setup routines. This is run at the start of each individual test, e.g. func TestSomething(t *testing.T), within your test suite.

func (*Fixture) UnitTestTeardown

func (fixture *Fixture) UnitTestTeardown(t *testing.T)

UnitTestTeardown will run all UnitTest teardown routines. This is run at the end of each individual test, e.g. func TestSomething(t *testing.T), within your test suite.

type FixtureConfig

type FixtureConfig struct {
	// AppRoot is an absolute path to the root of your Go application directory,
	// where your main.go file is located.
	AppRoot string

	// DatabaseSetups is a list of one or more database setup commands to run
	// before the test suite is run.
	DatabaseSetups []DB

	// AppSetup specifies configuration settings for your Go app executable.
	AppSetup App

	// DatabaseTeardowns is a list of one or more database teardown
	// commands to run after the test suite has run.
	DatabaseTeardowns []DB
}

FixtureConfig is a configuration object for your test Fixture.

type Script

type Script struct {
	// Type is the Script type to use.
	Type int

	// Command is either a literal database command, or a file
	// glob pattern, depending on the 'Type'.
	Command string
}

Script represents a database script run either as a setup or teardown routine. Command can either be a literal script, or a path (using globbing patterns) to a script file or files.

func NewScript

func NewScript(command string) Script

NewScript returns a Script that represents a literal database command to run.

func NewScriptPath

func NewScriptPath(path string) Script

NewScriptPath returns a Script that represents a glob path to a script files or files to run.

type UnitTest

type UnitTest struct {
	// DatabaseRoutines is a list of one or more database setup commands to
	// run before each unit, or at the end of each unit test.
	DatabaseRoutines []DB

	// Func is a function to run before each unit test is run.
	Func func(t *testing.T)
}

UnitTest represents database commands and a func to run at the beginning or end of each unit test.

Directories

Path Synopsis
tests
app

Jump to

Keyboard shortcuts

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