testhelper

package
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Jul 26, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Package testhelper is the Go family's TestHelper bundle.

Every other library in the family that ships a TestHelper ships it as its own `testhelper` sub-package, which leaves a consumer's SIT suite importing seven of them by hand and, worse, guessing which one owns a given fake. This package is the one import that answers that: it re-exports the sibling helpers under sibling-qualified names, so `ProblemAssertError`, `AuthNewFakeIDP`, and `PresetStartPostgres` say where they come from without a single import alias.

It bundles the seven helpers that EXIST. The core-utils sibling deliberately ships none — pure deterministic functions have no seam to fake — so there is nothing to re-export and no empty facade pretending otherwise.

The sibling packages remain importable directly. A consumer that wants the full surface of one helper should import it; this bundle carries the fakes, constructors, and assertion entry points a SIT suite reaches for, not a mirror of every symbol.

Its own glue

On top of the bundle it ships what only the harness can:

  • StartStack — Testcontainers glue that boots the four frozen infra presets together and emits the configuration blocks that address them. This is the DB-adapter integration tier (G1); telemetry infrastructure is never spun up, there is no fake OTLP collector, and real export is proven at SIT against the Garden preview environment instead.
  • NewScriptedDriver and EchoEntrypoint — drivers that let a consumer test its own journeys without a system under test.
  • AssertStep, AssertReport, and friends — the assertions a harness consumer would otherwise rewrite in every suite.

Everything here is proven by the meta tier, including the assertions themselves: each one is shown to pass on known-good input and fail on known-bad, because an assertion helper that cannot fail is worse than none.

Index

Examples

Constants

View Source
const ExitCodeVar = "E2E_ECHO_EXIT_CODE"

ExitCodeVar is the environment variable EchoEntrypoint reads its exit code from.

View Source
const PresetDefaultKey = presetth.DefaultKey

PresetDefaultKey is the connection key the preset helpers emit by default.

Variables

View Source
var (
	// ProblemExpectID expects a Problem whose type URI ends in the given id.
	ProblemExpectID = problemth.ExpectID
	// ProblemExpectType expects an exact Problem type URI.
	ProblemExpectType = problemth.ExpectType
	// ProblemExpectTitle expects a Problem title.
	ProblemExpectTitle = problemth.ExpectTitle
	// ProblemExpectStatus expects a Problem status code.
	ProblemExpectStatus = problemth.ExpectStatus
	// ProblemExpectDetail expects a Problem detail string.
	ProblemExpectDetail = problemth.ExpectDetail
	// ProblemExpectInstance expects a Problem instance reference.
	ProblemExpectInstance = problemth.ExpectInstance
	// ProblemExpectRecoverable expects a Problem's recoverable flag.
	ProblemExpectRecoverable = problemth.ExpectRecoverable
	// ProblemExpectData expects a Problem's `data` extension.
	ProblemExpectData = problemth.ExpectData
	// ProblemCheckError reports why an error is not the expected Problem.
	ProblemCheckError = problemth.CheckError
	// ProblemAssertError fails the test unless an error is the expected Problem.
	ProblemAssertError = problemth.AssertError
	// ProblemCheckProblem reports why a Problem envelope misses expectations.
	ProblemCheckProblem = problemth.CheckProblem
	// ProblemAssertProblem fails the test unless a Problem envelope matches.
	ProblemAssertProblem = problemth.AssertProblem
	// ProblemSampleErrorPortal is a ready error portal for fixtures.
	ProblemSampleErrorPortal = problemth.SampleErrorPortal
	// ProblemSample is a ready Problem envelope for fixtures.
	ProblemSample = problemth.SampleProblem
)
View Source
var (
	// NewInMemorySystem creates a deterministic clock and environment seam.
	NewInMemorySystem = interfacesth.NewInMemorySystem
	// NewInMemoryVfs creates an in-memory filesystem seam.
	NewInMemoryVfs = interfacesth.NewInMemoryVfs
	// NewInMemoryTerminal creates a scripted process-execution seam, which is
	// what lets the harness test its own compiled-artifact driver without ever
	// compiling an artifact.
	NewInMemoryTerminal = interfacesth.NewInMemoryTerminal
)
View Source
var (
	// ConfigRequireConfig fails the test unless a configuration loaded.
	ConfigRequireConfig = configth.RequireConfig
	// ConfigRequireLoadError fails the test unless loading failed.
	ConfigRequireLoadError = configth.RequireLoadError
	// ConfigRequireIssue fails the test unless a validation issue names a path.
	ConfigRequireIssue = configth.RequireIssue
	// ConfigStub builds a configuration straight from a raw map.
	ConfigStub = configth.StubConfig
	// ConfigStubApp builds a configuration carrying only an app block.
	ConfigStubApp = configth.StubApp
	// ConfigBaseSource wraps a base document as a YAML source.
	ConfigBaseSource = configth.BaseSource
	// ConfigOverlaySource wraps a landscape overlay as a YAML source.
	ConfigOverlaySource = configth.OverlaySource
	// ConfigEnvSource wraps environment variables as an env source.
	ConfigEnvSource = configth.EnvSource
	// ConfigInvalidSchemaBlock is a block that fails schema composition.
	ConfigInvalidSchemaBlock = configth.InvalidSchemaBlock
)
View Source
var (
	// NewOtelLoggerSink creates the in-memory logger-sink mock.
	NewOtelLoggerSink = otelth.NewInMemoryLoggerSink
	// NewOtelMetricsCollector creates the in-memory metrics-collector mock.
	NewOtelMetricsCollector = otelth.NewInMemoryMetricsCollector
	// NewOtelTraceEmitter creates the in-memory trace-emitter mock.
	NewOtelTraceEmitter = otelth.NewInMemoryTraceEmitter
	// OtelAssertLogRecords fails the test unless the emitted logs match.
	OtelAssertLogRecords = otelth.AssertLogRecords
	// OtelAssertMetricRecords fails the test unless the emitted metrics match.
	OtelAssertMetricRecords = otelth.AssertMetricRecords
	// OtelAssertTraceRecords fails the test unless the emitted traces match.
	OtelAssertTraceRecords = otelth.AssertTraceRecords
	// OtelAssertResourceAttributes fails the test unless the semconv and
	// `atomi.*` resource attributes match.
	OtelAssertResourceAttributes = otelth.AssertResourceAttributes
	// OtelSampleConfig is a ready otel configuration block.
	OtelSampleConfig = otelth.SampleConfig
	// OtelSampleIdentity is a ready otel application identity.
	OtelSampleIdentity = otelth.SampleIdentity
)
View Source
var (
	// AuthNewFakeIDP creates a fake IdP with real JWKS material.
	AuthNewFakeIDP = authth.NewFakeIDP
	// AuthNewFakeProvider creates a scripted token provider.
	AuthNewFakeProvider = authth.NewFakeProvider
	// AuthNewMemoryTokenStore creates an in-memory access-token store.
	AuthNewMemoryTokenStore = authth.NewMemoryTokenStore
	// AuthNewMemoryRefreshStore creates an in-memory rotating-refresh store.
	AuthNewMemoryRefreshStore = authth.NewMemoryRefreshStore
	// AuthAssertProblem fails the test unless an error is the expected auth
	// problem.
	AuthAssertProblem = authth.AssertAuthProblem
	// AuthAssertOwnershipDenied fails the test unless ownership was refused.
	AuthAssertOwnershipDenied = authth.AssertOwnershipDenied
)
View Source
var (
	// APINewFakeBackend creates a fake backend for the client tree.
	APINewFakeBackend = apith.NewFakeBackend
	// APINewFakeTree creates a fake multi-backend client tree.
	APINewFakeTree = apith.NewFakeTree
	// APINewFakeRetriever creates a fake per-backend token retriever.
	APINewFakeRetriever = apith.NewFakeRetriever
	// APICanned builds a canned Problem-envelope route.
	APICanned = apith.Canned
	// APIAssertProblem fails the test unless an error is the expected backend
	// problem.
	APIAssertProblem = apith.AssertProblem
	// APIAssertOutcome fails the test unless the 3-case classification matches.
	APIAssertOutcome = apith.AssertOutcome
)
View Source
var (
	// PresetStartPostgres boots a Postgres container and emits its block.
	PresetStartPostgres = presetth.StartPostgres
	// PresetStartCache boots a cache container and emits its block.
	PresetStartCache = presetth.StartCache
	// PresetStartKv boots a kv container and emits its block.
	PresetStartKv = presetth.StartKv
	// PresetStartStorage boots a storage container and emits its block.
	PresetStartStorage = presetth.StartStorage
	// PresetCreateBucket creates the bucket a storage entry addresses.
	PresetCreateBucket = presetth.CreateBucket
	// PresetFakePostgres is a Postgres block that addresses nothing, for tests
	// that need shape rather than a live dependency.
	PresetFakePostgres = presetth.FakePostgres
	// PresetFakeCache is a cache block that addresses nothing.
	PresetFakeCache = presetth.FakeCache
	// PresetFakeKv is a kv block that addresses nothing.
	PresetFakeKv = presetth.FakeKv
	// PresetFakeStorage is a storage block that addresses nothing.
	PresetFakeStorage = presetth.FakeStorage
)
View Source
var ErrEmptyStack = errors.New("testhelper: a container stack needs at least one preset")

ErrEmptyStack reports a stack that was asked to boot nothing.

View Source
var ErrScriptExhausted = errors.New("testhelper: the scripted driver has no answer left")

ErrScriptExhausted reports a scripted driver asked for more invocations than it was given answers for.

It is an error rather than a zero result because a silent zero would let a journey pass on steps the script never anticipated.

Functions

func AssertHarnessProblem

func AssertHarnessProblem(t TestingT, err error, id string) problem.Problem

AssertHarnessProblem fails the test unless err is the harness problem id names, and returns the envelope for further assertions.

Example
package main

import (
	"fmt"

	"github.com/AtomiCloud/diene.go-e2e/lib/e2e"
	"github.com/AtomiCloud/diene.go-e2e/testhelper"
)

// exampleT satisfies [testhelper.TestingT] outside a test binary, so the
// examples can show what a failing assertion says.
type exampleT struct{}

// Helper is a no-op outside a test binary.
func (exampleT) Helper() {}

// Fatalf prints instead of aborting.
func (exampleT) Fatalf(format string, args ...any) {
	fmt.Printf(format+"\n", args...)
}

// exampleProblems builds the harness problem factory on the shared sample
// portal.
func exampleProblems() *e2e.Problems {
	problems, err := testhelper.SampleProblems()
	if err != nil {
		panic(err)
	}
	return problems
}

func main() {
	err := exampleProblems().Raise(e2e.ProblemJourneyEmpty, "nothing to run", nil)
	envelope := testhelper.AssertHarnessProblem(exampleT{}, err, e2e.ProblemJourneyEmpty)
	fmt.Println(envelope.Status)
}
Output:
422

func AssertNoHarnessProblem

func AssertNoHarnessProblem(t TestingT, err error)

AssertNoHarnessProblem fails the test when err is set.

func AssertReport

func AssertReport(t TestingT, actual e2e.Report, journey e2e.Journey)

AssertReport fails the test unless actual describes a complete, passing run of journey.

Example
package main

import (
	"fmt"

	"github.com/AtomiCloud/diene.go-e2e/lib/e2e"
	"github.com/AtomiCloud/diene.go-e2e/testhelper"
)

// exampleT satisfies [testhelper.TestingT] outside a test binary, so the
// examples can show what a failing assertion says.
type exampleT struct{}

// Helper is a no-op outside a test binary.
func (exampleT) Helper() {}

// Fatalf prints instead of aborting.
func (exampleT) Fatalf(format string, args ...any) {
	fmt.Printf(format+"\n", args...)
}

func main() {
	journey := e2e.Journey{
		Name: "seeds",
		Steps: []e2e.Step{{
			Name:       "seed",
			Invocation: e2e.Invocation{},
			Expect:     e2e.Expectation{ExitCode: 0},
		}},
	}
	report := e2e.Report{Driver: "scripted", Journey: "seeds", Steps: []e2e.StepReport{
		{Name: "seed", Result: e2e.Result{ExitCode: 0}},
	}}
	testhelper.AssertReport(exampleT{}, report, journey)
	fmt.Println("report matches")
}
Output:
report matches

func AssertResult

func AssertResult(t TestingT, actual e2e.Result, want e2e.Expectation)

AssertResult fails the test unless actual satisfies want.

Example
package main

import (
	"fmt"

	"github.com/AtomiCloud/diene.go-e2e/lib/e2e"
	"github.com/AtomiCloud/diene.go-e2e/testhelper"
)

// exampleT satisfies [testhelper.TestingT] outside a test binary, so the
// examples can show what a failing assertion says.
type exampleT struct{}

// Helper is a no-op outside a test binary.
func (exampleT) Helper() {}

// Fatalf prints instead of aborting.
func (exampleT) Fatalf(format string, args ...any) {
	fmt.Printf(format+"\n", args...)
}

func main() {
	testhelper.AssertResult(exampleT{}, e2e.Result{ExitCode: 0, Stdout: "ready"},
		e2e.Expectation{ExitCode: 0, StdoutContains: []string{"ready"}})
	testhelper.AssertResult(exampleT{}, e2e.Result{ExitCode: 1},
		e2e.Expectation{ExitCode: 0})
}
Output:
unexpected result: step "result": exit code 1 is not 0

func AssertStep

func AssertStep(t TestingT, step e2e.Step, actual e2e.Result)

AssertStep fails the test unless a step's observed result meets its expectation.

func CheckHarnessProblem

func CheckHarnessProblem(err error, id string) (problem.Problem, error)

CheckHarnessProblem reports why err is not the harness problem id names.

func CheckReport

func CheckReport(actual e2e.Report, journey e2e.Journey) error

CheckReport reports why a report does not describe the journey it should.

It proves the report covers every step of journey, in order, which is the assertion that catches a driver that silently skipped one — the failure mode a per-step assertion structurally cannot see.

func CheckResult

func CheckResult(actual e2e.Result, want e2e.Expectation) error

CheckResult reports why a result misses want, or nil when it matches.

Only the fields want actually sets are compared: a zero expected exit code with no output fragments asserts a clean run and nothing more, so a caller never has to restate output it does not care about.

func CheckStep

func CheckStep(step e2e.Step, actual e2e.Result) error

CheckStep reports why a step's observed result misses its own expectation.

func EchoEntrypoint

func EchoEntrypoint(_ context.Context, invocation e2e.Invocation, stdout io.Writer, stderr io.Writer) (int, error)

EchoEntrypoint is an e2e.Entrypoint that writes back what it was given.

It is the in-process half of a parity fixture: a compiled artifact that echoes its arguments and environment behaves identically, so a consumer can prove its parity wiring works before wiring in a real service.

The exit code is the value of the ExitCodeVar environment variable, defaulting to zero, which is what lets one entrypoint drive both the passing and the failing halves of a journey suite.

func NewEchoDriver

func NewEchoDriver(problems *e2e.Problems) (*e2e.InProcessDriver, error)

NewEchoDriver creates an in-process driver over EchoEntrypoint.

Example
package main

import (
	"context"
	"fmt"

	"github.com/AtomiCloud/diene.go-e2e/lib/e2e"
	"github.com/AtomiCloud/diene.go-e2e/testhelper"
)

// exampleProblems builds the harness problem factory on the shared sample
// portal.
func exampleProblems() *e2e.Problems {
	problems, err := testhelper.SampleProblems()
	if err != nil {
		panic(err)
	}
	return problems
}

func main() {
	driver, err := testhelper.NewEchoDriver(exampleProblems())
	if err != nil {
		panic(err)
	}
	result, err := driver.Run(context.Background(), e2e.Invocation{
		Args: []string{"seed"},
		Env:  map[string]string{"MARKER": "one"},
	})
	if err != nil {
		panic(err)
	}
	fmt.Print(result.Stdout)
}
Output:
args: seed
env: MARKER=one

func PresetRequireEntry

func PresetRequireEntry[Entry any](t TestingT, block map[string]Entry, key string) Entry

PresetRequireEntry fails the test unless a keyed preset block carries key.

It is a wrapper rather than an alias because Go cannot bind a generic function to a variable, and a bundle that silently dropped the generic helpers would send consumers back to the sibling import it exists to replace.

func PresetRequireStarted

func PresetRequireStarted[Started any](t TestingT, started *Started, err error) *Started

PresetRequireStarted fails the test unless a preset container started.

func RequireReport

func RequireReport(t TestingT, report e2e.Report, err error) e2e.Report

RequireReport fails the test unless a journey run succeeded, and returns its report.

func SampleProblems

func SampleProblems() (*e2e.Problems, error)

SampleProblems builds a harness problem factory on the shared sample error portal, so a consumer's first test needs no portal of its own.

Example
package main

import (
	"fmt"

	"github.com/AtomiCloud/diene.go-e2e/lib/e2e"
	"github.com/AtomiCloud/diene.go-e2e/testhelper"
)

// exampleProblems builds the harness problem factory on the shared sample
// portal.
func exampleProblems() *e2e.Problems {
	problems, err := testhelper.SampleProblems()
	if err != nil {
		panic(err)
	}
	return problems
}

func main() {
	problems := exampleProblems()
	fmt.Println(len(problems.Registry().Entries()))
}
Output:
10

Types

type APIFakeBackend

type APIFakeBackend = apith.FakeBackend

APIFakeBackend is the api-engine sibling's fake HTTP backend.

type APIFakeBackendOptions

type APIFakeBackendOptions = apith.FakeBackendOptions

APIFakeBackendOptions configures an APIFakeBackend.

type APIProblemOptions

type APIProblemOptions = apith.ProblemOptions

APIProblemOptions describes a canned Problem-envelope response.

type APIRoute

type APIRoute = apith.Route

APIRoute is one canned route on a fake backend.

type AuthFakeIDP

type AuthFakeIDP = authth.FakeIDP

AuthFakeIDP is the auth-engine sibling's fake identity provider.

type AuthFakeIDPOptions

type AuthFakeIDPOptions = authth.FakeIDPOptions

AuthFakeIDPOptions configures an AuthFakeIDP.

type AuthFakeProvider

type AuthFakeProvider = authth.FakeProvider

AuthFakeProvider is the auth-engine sibling's scripted token provider.

type AuthFakeProviderOptions

type AuthFakeProviderOptions = authth.FakeProviderOptions

AuthFakeProviderOptions configures an AuthFakeProvider.

type InMemorySystem

type InMemorySystem = interfacesth.InMemorySystem

InMemorySystem is the interfaces sibling's deterministic System seam.

type InMemorySystemOptions

type InMemorySystemOptions = interfacesth.InMemorySystemOptions

InMemorySystemOptions configures an InMemorySystem.

type InMemoryTerminal

type InMemoryTerminal = interfacesth.InMemoryTerminal

InMemoryTerminal is the interfaces sibling's scripted Terminal seam.

type InMemoryVfs

type InMemoryVfs = interfacesth.InMemoryVfs

InMemoryVfs is the interfaces sibling's in-memory filesystem seam.

type InMemoryVfsOptions

type InMemoryVfsOptions = interfacesth.InMemoryVfsOptions

InMemoryVfsOptions configures an InMemoryVfs.

type OtelLoggerSink

type OtelLoggerSink = otelth.InMemoryLoggerSink

OtelLoggerSink is the otel sibling's in-memory LoggerSink mock.

type OtelMetricsCollector

type OtelMetricsCollector = otelth.InMemoryMetricsCollector

OtelMetricsCollector is the otel sibling's in-memory MetricsCollector mock.

type OtelTraceEmitter

type OtelTraceEmitter = otelth.InMemoryTraceEmitter

OtelTraceEmitter is the otel sibling's in-memory TraceEmitter mock.

type PresetContainer

type PresetContainer = presetth.Container

PresetContainer is one started container.

type PresetDockerRuntime

type PresetDockerRuntime = presetth.DockerRuntime

PresetDockerRuntime is the real Docker-backed runtime.

type PresetPostgresOptions

type PresetPostgresOptions = presetth.PostgresOptions

PresetPostgresOptions configures a Postgres preset container.

type PresetRedisOptions

type PresetRedisOptions = presetth.RedisOptions

PresetRedisOptions configures a cache or kv preset container.

type PresetRuntime

type PresetRuntime = presetth.Runtime

PresetRuntime is the container runtime the preset helpers start on.

type PresetStartedCache

type PresetStartedCache = presetth.StartedCache

PresetStartedCache is a started cache preset and its config block.

type PresetStartedKv

type PresetStartedKv = presetth.StartedKv

PresetStartedKv is a started kv preset and its config block.

type PresetStartedPostgres

type PresetStartedPostgres = presetth.StartedPostgres

PresetStartedPostgres is a started Postgres preset and its config block.

type PresetStartedStorage

type PresetStartedStorage = presetth.StartedStorage

PresetStartedStorage is a started storage preset and its config block.

type PresetStorageOptions

type PresetStorageOptions = presetth.StorageOptions

PresetStorageOptions configures a storage preset container.

type ProblemOption

type ProblemOption = problemth.Option

ProblemOption is one expectation about a Problem envelope.

type ScriptedDriver

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

ScriptedDriver answers invocations from a script and records what it was asked.

It is how a consumer tests its OWN journeys before a system under test exists, and how the harness tests the journey runner without a process. It is safe for concurrent use, because a parity run drives two drivers and a consumer will eventually run them in parallel.

func NewScriptedDriver

func NewScriptedDriver(label string, steps ...ScriptedStep) *ScriptedDriver

NewScriptedDriver creates a driver that answers from steps in order.

Example
package main

import (
	"context"
	"fmt"

	"github.com/AtomiCloud/diene.go-e2e/lib/e2e"
	"github.com/AtomiCloud/diene.go-e2e/testhelper"
)

func main() {
	driver := testhelper.NewScriptedDriver(
		"scripted",
		testhelper.ScriptedStep{Result: e2e.Result{ExitCode: 0, Stdout: "seeded"}},
	)
	result, err := driver.Run(context.Background(), e2e.Invocation{Args: []string{"seed"}})
	if err != nil {
		panic(err)
	}
	fmt.Println(driver.Name(), result.Stdout, driver.Remaining())
	fmt.Println(driver.Calls()[0].Args)
}
Output:
scripted seeded 0
[seed]
Example (Exhausted)
package main

import (
	"context"
	"fmt"

	"github.com/AtomiCloud/diene.go-e2e/lib/e2e"
	"github.com/AtomiCloud/diene.go-e2e/testhelper"
)

func main() {
	// A script that runs out REFUSES rather than inventing a zero result, because
	// a silent zero would let a journey pass on a step nobody anticipated.
	driver := testhelper.NewScriptedDriver("scripted")
	_, err := driver.Run(context.Background(), e2e.Invocation{})
	fmt.Println(err)
}
Output:
testhelper: the scripted driver has no answer left

func (*ScriptedDriver) Calls

func (d *ScriptedDriver) Calls() []e2e.Invocation

Calls returns the invocations the driver was asked to run, in order.

Each one is an independent copy, so reading recorded history cannot rewrite it — a caller that inspects an argument slice and then reuses it would otherwise corrupt the record it just read.

func (*ScriptedDriver) Name

func (d *ScriptedDriver) Name() string

Name identifies the driver in reports and parity failures.

func (*ScriptedDriver) Remaining

func (d *ScriptedDriver) Remaining() int

Remaining reports how many scripted answers are unused, so a test can prove its journey consumed the whole script rather than stopping early.

func (*ScriptedDriver) Run

func (d *ScriptedDriver) Run(_ context.Context, invocation e2e.Invocation) (e2e.Result, error)

Run records the invocation and returns the next scripted answer.

type ScriptedStep

type ScriptedStep struct {
	// Result is what the driver reports for this invocation.
	Result e2e.Result
	// Err, when set, is returned instead of the result: the invocation could
	// not be carried out at all.
	Err error
}

ScriptedStep is one canned answer a ScriptedDriver gives.

type Stack

type Stack struct {
	// Postgres is the started Postgres preset, or nil.
	Postgres *PresetStartedPostgres
	// Cache is the started cache preset, or nil.
	Cache *PresetStartedCache
	// Kv is the started kv preset, or nil.
	Kv *PresetStartedKv
	// Storage is the started storage preset, or nil.
	Storage *PresetStartedStorage
}

Stack is a booted set of infra preset containers and the configuration blocks that address them.

This is the DB-adapter integration tier (G1) and nothing more. It boots DATA dependencies; it never boots telemetry infrastructure, and there is no fake OTLP collector here or anywhere else in this module — the otel interface mocks cover emission at the integration tier, and real export is a SIT concern against the Garden preview environment.

func RequireStack

func RequireStack(t TestingT, stack *Stack, err error) *Stack

RequireStack fails the test unless a stack booted, and registers nothing: terminating it stays the caller's decision, because a suite usually shares one stack across many tests.

func StartStack

func StartStack(ctx context.Context, options StackOptions) (*Stack, error)

StartStack boots the selected presets and returns them together.

A partial failure terminates whatever already started before returning, so a failed boot never leaks a container into the next test. Selecting nothing is a ErrEmptyStack rather than an empty success: a test that boots no dependency does not want this helper.

Example
package main

import (
	"context"
	"fmt"

	"github.com/AtomiCloud/diene.go-e2e/testhelper"
)

func main() {
	// Nothing is booted by default: a suite pays only for the dependencies it
	// actually needs.
	_, err := testhelper.StartStack(context.Background(), testhelper.StackOptions{})
	fmt.Println(err)
}
Output:
testhelper: a container stack needs at least one preset

func (*Stack) Blocks

func (s *Stack) Blocks() map[string]any

Blocks renders the started presets as the configuration document fragment that addresses them.

This is the glue the whole helper exists for: a Testcontainers port is only known at run time, so the block a service loads has to be BUILT from the started containers rather than written down in a fixture.

func (*Stack) Terminate

func (s *Stack) Terminate(ctx context.Context) error

Terminate stops every started container, reporting the first failure but always attempting all of them.

type StackOptions

type StackOptions struct {
	// Key is the connection key every started preset is emitted under. Blank
	// uses [PresetDefaultKey].
	Key string
	// Postgres boots the Postgres preset.
	Postgres bool
	// Cache boots the cache preset.
	Cache bool
	// Kv boots the kv preset.
	Kv bool
	// Storage boots the storage preset; the preset helper creates its bucket.
	Storage bool
	// Runtime overrides the container runtime. Nil uses the real Docker
	// runtime.
	Runtime PresetRuntime
}

StackOptions selects which infra presets a Stack boots.

Nothing is booted by default. An integration test that needs Postgres should pay for Postgres and nothing else, because a suite that boots four containers to test one adapter is a suite people stop running.

type TestingT

type TestingT interface {
	// Helper marks the caller as a test helper.
	Helper()
	// Fatalf reports a fatal failure.
	Fatalf(format string, args ...any)
}

TestingT is the slice of *testing.T the harness assertions need.

It is an interface rather than *testing.T so the helpers can be proven to FAIL, which a helper that only ever ran against a real *testing.T could not be: the meta tier hands them a recorder and asserts on what they reported.

Jump to

Keyboard shortcuts

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