README
¶
Integration
This package contains helper functions that support integration testing of Go services and applications. The main library utilities are listed below.
- Starting and stopping Docker containers inside Go tests
- Creating JSON Web Tokens for testing gRPC and REST requests
- Launching a Postgres database to tests against
- Building and executing Go binaries
Examples
Start and Stop Docker Containers
Testing your application or service in isolation might be impossible. Your project may require a database or supporting services in order to function reliably.
Enter Docker containers.
You can prop-up Docker containers to substitute backend databases or services. This package is intended to make that process easier with helper functions. To start out, you might want to familiarize yourself with Go's built-in TestMain
function, but here's the basic gist.
It is sometimes necessary for a test program to do extra setup or teardown before or after testing. It is also sometimes necessary for a test to control which code runs on the main thread.
TestMain
runs in the main goroutine and can do whatever setup and teardown is necessary.
Awesome. Example time!
import (
"log"
"testing"
"github.com/infobloxopen/atlas-app-toolkit/integration"
)
// TestMain does pre-test set up
func TestMain(m *testing.M) {
// RunContainer takes a docker image, docker run arguments, and
// runtime-specific arguments
stop, err := integration.RunContainer(
"redis:latest",
[]string{
"--publish=6380:6380",
"--env=REDIS_PASSWORD=password",
"--rm",
},
[]string{
"maxmemory 2mb",
},
)
if err != nil {
log.Fatal("unable to start test redis container")
}
// stop and remove container after testing
defer stop()
m.Run()
}
Launching a Postgres Database
Your application or service might be backed by a Postgres database. This library can help configure, launch, and reset a Postgres database between tests.
To start out, here's how you can use the Postgres options to configure your database.
import (
"database/sql"
"log"
"testing"
"github.com/infobloxopen/atlas-app-toolkit/integration"
_ "github.com/lib/pq"
)
var (
myTestDatabase integration.TestPostgresDB
)
func TestMain(m *testing.M) {
// myMigrateFunc describes how to build the database schema from scratch
myMigrateFunc := func(db *sql.DB) error {
// migration.Run(db) isn't a real function, but let's pretend it
// creates some tables in a database
return migration.Run(db)
}
config, err := integration.NewTestPostgresDB(
integration.WithName("my_database_name"),
// passing a migrate up function will allow the database schema to be
// destroyed and re-built, effectively causing the database to reset
integration.WithMigrateUpFunction(myMigrateFunc),
)
if err != nil {
log.Fatal("unable to build postgres config")
}
myTestDatabase = config
stop, err := myTestDatabase.RunAsDockerContainer()
if err != nil {
log.Fatal("unable to start test database")
}
defer stop()
m.Run()
}
The example above will configure and launch the database. The code below shows how to reset the database between tests.
import (
"testing"
)
func TestMyEndpoint(t *testing.T) {
// reset the database before running tests. you would want to do this if any
// other tests create or modify entries in the database
if err := myTestDatabase.Reset(); err != nil{
t.Fatalf("unable to reset database schema: %v", err)
}
...
}
Building and Running Go Binaries
If you want to test your Go application or service, you'll need to build it first. The integration
package provides helpers that enable you to build your Go binary and run it locally.
Alternatively, you can build your application or service's Docker image, then run the Docker image by following the [earlier examples](#Start and Stop Docker Containers).
Building the Binary
import (
"log"
"testing"
"github.com/infobloxopen/atlas-app-toolkit/integration"
)
func TestMain(m *testing.M) {
remove, err := integration.BuildGoSource("./path/to/my/go/package", "binaryName")
if err != nil {
log.Fatalf("unable to build go binary: %v", err)
}
// this will delete the binary after the tests run
defer remove()
m.Run()
}
Running the Binary
import (
"log"
"testing"
"github.com/infobloxopen/atlas-app-toolkit/integration"
)
func TestMain(m *testing.M) {
stop, err := integration.RunBinary(
"./path/to/my/go/package/binaryName",
// provide as many command-line arguments as you like
"-debug=true",
)
if err != nil {
log.Fatalf("unable to run go binary: %v", err)
}
// this will stop the running process
defer stop()
m.Run()
}
Finding Open Ports
To help avoid port conflicts, the integration
package provides a simple helper that finds an port on the testing machine.
import (
"log"
"testing"
"github.com/infobloxopen/atlas-app-toolkit/integration"
)
func TestMain(m *testing.M) {
port, err := integration.GetOpenPort()
if err != nil {
log.Fatalf("unable to find open port: %v", err)
}
}
You can also specify a port range.
import (
"log"
"testing"
"github.com/infobloxopen/atlas-app-toolkit/integration"
)
func TestMain(m *testing.M) {
port, err := integration.GetOpenPortInRange(6000, 8000)
if err != nil {
log.Fatalf("unable to find open port: %v", err)
}
}
Creating JSON Web Tokens
If you plan to run test requests against your application or service, you might need to provide a JWT for authentication purposes. This isn't terribly tricky, but it's nice to have some helpers that spare you from reinventing the wheel.
Using the Standard Token
If you just need a token, but don't particularly care what it contains, then you might want to use the standard token. The term standard just means the token has the minimum required JWT claims that are needed to authenticate.
import (
"net/http"
"testing"
"github.com/infobloxopen/atlas-app-toolkit/integration"
)
func TestMyEndpoint(t *testing.T) {
token, err := integration.StandardTestJWT()
if err != nil {
t.Fatalf("unable to generate test token: %v", err)
}
req, err := http.NewRequest(http.MethodGet, "/endpoint", nil)
if err != nil {
t.Fatalf("unable to generate test request: %v", err)
}
req.Header.Set("Authorization: Bearer %s", token)
...
}
Creating Default Test Requests
You might want to create REST requests or gRPC requests that use the standard JWT. Rather than write code that packs the JWT into the HTTP request header, or the gRPC request context, the integration library has utilities to do this for you.
Here's how you would be a test HTTP request.
import (
"net/http"
"testing"
"github.com/infobloxopen/atlas-app-toolkit/integration"
)
func TestMyEndpoint(t *testing.T) {
client := http.Client{}
req, err := integration.MakeStandardRequest(
http.MethodGet, "/endpoint", map[string]string{
"message": "hello world",
},
)
if err != nil {
t.Fatalf("unable to build test http request: %v", err)
}
res, err := client.Do(req)
...
}
And the same for gRPC requests.
import (
"testing"
"github.com/infobloxopen/atlas-app-toolkit/integration"
)
func TestMyGRPCEndpoint(t *testing.T) {
ctx, err := integration.StandardTestingContext()
if err != nil {
t.Fatalf("unable to build test grpc context: %v", err)
}
gRPCResponseMessage, err := gRPCClient.MyGRPCEndpoint(ctx, gRPCRequestMessage)
if err != nil {
t.Fatalf("unable to send grpc request: %v", err)
}
...
}
Documentation
¶
Index ¶
- Variables
- func AppendTokenToOutgoingContext(ctx context.Context, fieldName, token string) context.Context
- func BuildGoSource(packagePath, output string) (func() error, error)
- func GetOpenPort() (int, error)
- func GetOpenPortInRange(lowerBound, upperBound int) (int, error)
- func MakeStandardRequest(method, url string, payload interface{}) (*http.Request, error)
- func MakeTestJWT(method jwt.SigningMethod, claims jwt.Claims) (string, error)
- func NewTestPostgresDB(opts ...option) (testPostgresDB, error)
- func RunBinary(binPath string, args ...string) (func(), error)
- func RunContainer(image string, dockerArgs, runtimeArgs []string) (func() error, error)
- func StandardTestJWT() (string, error)
- func StandardTestingContext() (context.Context, error)
- func WithMigrateDownFunction(migrateDownFunction func(*sql.DB) error) func(*testPostgresDB)
- func WithMigrateUpFunction(migrateUpFunction func(*sql.DB) error) func(*testPostgresDB)
- func WithName(name string) func(*testPostgresDB)
- func WithPassword(password string) func(*testPostgresDB)
- func WithPort(port int) func(*testPostgresDB)
- func WithTimeout(timeout time.Duration) func(*testPostgresDB)
- func WithUser(user string) func(*testPostgresDB)
- func WithVersion(version string) func(*testPostgresDB)
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // StandardClaims is the standard payload inside a test JWT StandardClaims = jwt.MapClaims{ auth.MultiTenancyField: "TestAccount", } )
Functions ¶
func AppendTokenToOutgoingContext ¶
AppendTokenToOutgoingContext adds an authorization token to the gRPC request context metadata. The user must provide a token field name like "token" or "bearer" to this function. It is intended specifically for gRPC testing.
Example (Output) ¶
Output: true true
func BuildGoSource ¶
BuildGoSource builds a target Go package and gives the resulting binary some user-defined name. The function returned by BuildGoSource will remove the binary that got created.
func GetOpenPort ¶
GetOpenPort searches for an open port on the host
func GetOpenPortInRange ¶
GetOpenPortInRange finds the first unused port within specific range
func MakeStandardRequest ¶
MakeStandardRequest issues an HTTP request a specific endpoint with Atlas-specific request data (e.g. the authorization token)
func MakeTestJWT ¶
MakeTestJWT generates a token string based on the given JWT claims
func NewTestPostgresDB ¶
func NewTestPostgresDB(opts ...option) (testPostgresDB, error)
NewTestPostgresDB returns a test postgres database that the functional options that have been provided by the caller
func RunContainer ¶
RunContainer launches a detached docker container on the host machine. It takes an image name, a list of "docker run" arguments, and a list of arguments that get passed to the container runtime
func StandardTestJWT ¶
StandardTestJWT builds a JWT with the standard test claims in the JWT payload
func StandardTestingContext ¶
StandardTestingContext returns an outgoing request context that includes the standard test JWT. It is intended specifically for gRPC testing.
func WithMigrateDownFunction ¶
WithMigrateFunction is used to tear down the test Postgres according to a specific set of migrations. It runs on a per-test basis whenever the Reset() function is called.
func WithMigrateUpFunction ¶
WithMigrateFunction is used to rebuild the test Postgres database on a per-test basis. Whenever the database is reset with the Reset() function, the migrateUp function will rebuild the tables.
func WithName ¶
func WithName(name string) func(*testPostgresDB)
WithName is used to specify the name of the test Postgres database. By default the database name is "test-postgres-db"
func WithPassword ¶
func WithPassword(password string) func(*testPostgresDB)
WithPassword is used to specify the password of the test Postgres database
func WithPort ¶
func WithPort(port int) func(*testPostgresDB)
WithPort is used to specify the port of the test Postgres database. By default, the test database will find the first open port in the 35000+ range
func WithTimeout ¶
WithTimeout is used to specify a connection timeout to the database
func WithUser ¶
func WithUser(user string) func(*testPostgresDB)
WithUser is used to specify the name of the Postgres user that owns the test database
func WithVersion ¶
func WithVersion(version string) func(*testPostgresDB)
WithVersion is used to specify the version of the test Postgres database. By default the version is "latest"
Types ¶
This section is empty.