tstr

package module
v0.2.1 Latest Latest
Warning

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

Go to latest
Published: May 26, 2025 License: Apache-2.0 Imports: 6 Imported by: 0

README

Go Reference codecov main

tstr: your ultimate testing library

tstr allows you to write application, integration and black-box tests like normal unit tests in Go.

With tstr you can easily declare the test dependencies like:

  • compose files
  • single containers
  • cli commands
  • main package of Go program

and let tstr take care of the rest.

Table of Contents

Usage

This library is build on top of two concepts:

  • tstr.Tester
  • tstr.Dependency
tstr.Tester

There's two common ways to use tester, either from func TestMain or from func TestXXX. For both of these approaches there's helper function; tstr.RunMain and tstr.Run, which make it easy to setup and run tstr.Tester.

tstr.RunMain
func TestMain(m *testing.M) {
    tstr.RunMain(m, tstr.WithDeps(
    // Pass test dependencies here.
    ))
}

With TestMain approach you will have single test env within the packge. tstr.RunMain will setup the test env you defined, call m.Run(), cleanup test env and finally call os.Exit with returned exit code.

tstr.Run

This approach allows more granular control over test env. For example you can have single test env for each top level test. This can be usefull when you want to avoid any side effects and shared state between tests. Also this approach allows more advaced usage like creating a pool of test envs for parallel testing.

tstr.WithFn

Simplest way to use tstr.Run is with the tstr.WithFn option:

func TestMyFunc(t *testing.T) {
    err := tstr.Run(
        tstr.WithDeps(
        // Pass test dependencies here.
        ),
        tstr.WithFn(func() {
            const (
                input    = 1
                expected = 1
            )
            got := MyFunc(input)
            assert.Equal(t, expected, got)
        }),
    )
    require.NoError(t, err)
}
tstr.WithTable

For table driven tests you can use tstr.WithTable which loops over the given test table and executes test function for each element using t.Run:

func TestMyFunc(t *testing.T) {
    type test struct {
        Name     string
        input    int
        expected int
    }

    tests := []test{
        {Name: "test-1", input: 1, expected: 1},
        {Name: "test-2", input: 2, expected: 2},
        {Name: "test-3", input: 3, expected: 3},
    }

    err := tstr.Run(
        tstr.WithDeps(
        // Add dependencies here.
        ),
        tstr.WithTable(t, tests, func(t *testing.T, tt test) {
            got := MyFunc(tt.input)
            assert.Equal(t, tt.expected, got)
        }),
    )
    require.NoError(t, err)
}
tstr.Dependency

tstr.Dependency declares an interface for test dependency which can be then controlled by tstr.Tester. This repo provides the most commonly used dependecies that user can use within their tests. Since tstr.Dependency is just an interface users can also implement their own custom dependencies.

Compose

Compose dependecy allows you to define and manage Docker Compose stacks as test dependencies. You can create a Compose stack from projects compose file and control its lifecycle within your tests.

func TestMain(m *testing.M) {
    tstr.RunMain(m, tstr.WithDeps(
        compose.New(
            compose.WithFile("../docker-compose.yaml"),
            compose.WithUpOptions(tc.Wait(true)),
            compose.WithDownOptions(tc.RemoveVolumes(true)),
            compose.WithEnv(map[string]string{"DB_PORT": "5432"}),
            compose.WithWaitForService("postgres", wait.ForLog("ready to accept connections")),
        ),
    ))
}
Container

Container dependecy allows you to define and manage single containers as test dependencies. You can use predefined modules from testcontainer-go or create generic container.

func TestMain(m *testing.M) {
    tstr.RunMain(m, tstr.WithDeps(
        container.New(
            container.WithModule(postgres.Run, "postgres:16-alpine",
                postgres.WithDatabase("test"),
                postgres.WithUsername("user"),
                postgres.WithPassword("password"),
            ),
        ),
    ))
}
Cmd

Cmd dependecy is the most versatile dependency. It can be used for running any binary or even compiling a Go application and running it as dependency.

cmd.WithCommand

cmd.WithCommand allows using binaries from

func TestMain(m *testing.M) {
    tstr.RunMain(m, tstr.WithDeps(
        cmd.New(
            cmd.WithCommand("my-app", "--listen", ":8080"),
            cmd.WithWaitMatchingLine("Server ready"),
        ),
    ))
}
cmd.WithGoCode

This example compiles my-app Go application, instruments it for coverage collections, waits for it to be ready and finally starts running tests.

func TestMain(m *testing.M) {
    tstr.RunMain(m, tstr.WithDeps(
        cmd.New(
            cmd.WithGoCode("../", "./cmd/my-app"),
            cmd.WithReadyHTTP("http://localhost:8080/ready"),
            cmd.WithEnvAppend("GOCOVERDIR=./cover"),
        ),
    ))
}
Custom Dependencies

You can also create your own custom dependencies by implementing the tstr.Dependency interface.

type Custom struct{}

func New() *Custom {
    return &Custom{}
}

func (c *Custom) Start() error { return nil }

func (c *Custom) Ready() error { return nil }

func (c *Custom) Stop() error { return nil }

Acknowledgements

This library is based on the work originally done as part of (https://github.com/elisasre/go-common)[https://github.com/elisasre/go-common] and was extracted to it's own repo to be more approachable by users.

Documentation

Overview

Package tstr provides testing framework for Go programs that simplifies testing with test dependencies.

Index

Constants

View Source
const (
	ErrStartFailed = strerr.Error("failed to start test dependencies")
	ErrStopFailed  = strerr.Error("failed to stop test dependencies")
)
View Source
const (
	ErrMissingTestFn     = strerr.Error("missing Opt for test function")
	ErrOverwritingTestFn = strerr.Error("trying to overwrite test function")
	ErrMissingNameField  = strerr.Error("missing field Name in test case struct")
	ErrWrongTestCaseType = strerr.Error("wrong test case type")
)

Variables

This section is empty.

Functions

func Run

func Run(opts ...Opt) error

Run runs the test with the given options. Options are applied in the order they are passed. One of the options must provide the test function.

Options that provide the test function:

  • WithM
  • WithFn
  • WithTable

func RunMain

func RunMain(m TestingM, opts ...Opt)

RunMain is a convinience wrapper around Run that can be used inside TestMain. RunMain applies automatically WithM option which calls m.Run. Also os.Exit is called with non-zero exit code if Run returns any error.

Example:

func TestMain(m *testing.M) {
	tstr.RunMain(m, tstr.WithDeps(MyDependency()))
}

Types

type Dependency

type Dependency interface {
	Startable
	Stoppable
}

type ExitError

type ExitError int

func (ExitError) Error

func (e ExitError) Error() string

type Opt

type Opt func(*Tester) error

func WithDeps

func WithDeps(deps ...Dependency) Opt

WithDeps adds dependencies to the tester.

func WithFn

func WithFn(fn func()) Opt

WithFn uses the given function as the test function.

func WithM

func WithM(m TestingM) Opt

WithM uses the given testing.M as the test function.

func WithTable

func WithTable[T any](tt TestingT, cases []T, test func(*testing.T, T)) Opt

WithTable runs the given test function for each test case in the table.

type Runner

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

func NewRunner

func NewRunner(rr ...Dependency) *Runner

NewRunner creates a new Runner with the given dependencies. The dependencies will be started in the order they are provided and stopped in reverse order. If any of the dependencies fail to start, the rest of the dependencies will not be started. If any of the dependencies fail to stop, the rest of the dependencies will still be stopped. Start method should be non-blocking and return immediately after starting the dependency. Ready method should block until the dependency is ready to be used. Stop method should block until the dependency is stopped.

func (*Runner) Start

func (t *Runner) Start() error

Start starts the dependencies in the order they were provided and waits for them to be ready. Next dependency will not be started if the previous one fails to start or become ready. Stop should be always called after Start, even if Start fails. That way all the started dependencies will be stopped.

func (*Runner) Stop

func (t *Runner) Stop() error

Stop stops all started dependencies in the reverse order they were started.

type Startable

type Startable interface {
	Start() error
	Ready() error
}

type Stoppable

type Stoppable interface {
	Stop() error
}

type Tester

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

func NewTester

func NewTester(opts ...Opt) *Tester

NewTester creates a new Tester with the given options. In most cases you should use Run function instead of creating Tester manually, Using NewTester can be useful if you need more control over the test execution or if you want to reuse same Tester instance.

func (*Tester) Init

func (t *Tester) Init() error

Init applies all options to the Tester.

func (*Tester) Run

func (t *Tester) Run() error

Run starts the test dependencies, executes the test function and finally stops the dependencies.

type TestingM

type TestingM interface {
	Run() int
}

TestingM contains required methods from *testing.M.

type TestingT

type TestingT interface {
	Run(name string, fn func(*testing.T)) bool
}

TestingT contains required methods from *testing.T.

Directories

Path Synopsis
dep
cmd
container
Package container provides a wrapper around the testcontainers-go library to simplify container management in tests.
Package container provides a wrapper around the testcontainers-go library to simplify container management in tests.
Package strerr provides string error type that allows declaring errors as constants unlike Go's own errors.New which uses privete struct type.
Package strerr provides string error type that allows declaring errors as constants unlike Go's own errors.New which uses privete struct type.

Jump to

Keyboard shortcuts

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