testctx

package module
v0.0.5 Latest Latest
Warning

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

Go to latest
Published: Jun 9, 2025 License: Apache-2.0 Imports: 5 Imported by: 3

README

testctx

Package testctx extends Go's testing package with support for context.Context and middleware.

The main benefits are:

  • Context Support: Tests and benchmarks receive a context.Context that's automatically canceled when they complete
  • Middleware: Add behaviors like timeouts, tracing, or parallel execution across entire test suites
  • Composable: Mix and match middleware to build the exact test environment you need
  • Go-native: Works seamlessly with existing Go test patterns and tools (no custom CLI, etc.)

Example

type MySuite struct{}

func (t *MySuite) TestExample(ctx context.Context, t *testctx.T) {
    // ...
}

func (t *MySuite) BenchmarkExample(ctx context.Context, b *testctx.B) {
    // ...
}

func TestAll(t *testing.T) {
	testctx.New(t,
		testctx.WithTimeout(time.Minute), // each test has a 1 minute timeout
		testctx.WithParallel(), // run tests in parallel
	).RunTests(&MySuite{}) // runs TestExample
}

func BenchAll(t *testing.T) {
	testctx.New(t,
		testctx.WithTimeout(10 * time.Minute), // each benchmark has a 10 minute timeout
	).RunBenchmarks(&MySuite{}) // runs BenchmarkExample
}

The oteltest package provides middleware for transparent tracing:

package oteltest_test

import (
	"context"
	"os"
	"testing"
	"time"

	"github.com/dagger/testctx"
	"github.com/dagger/testctx/oteltest"
)

func TestMain(m *testing.M) {
	os.Exit(oteltest.Main(m)) // auto-wires OTel exporters
}

func TestFruits(t *testing.T) {
	testctx.New(t,
		testctx.WithParallel(), // run tests in parallel
		oteltest.WithTracing[*testing.T](), // trace each test and subtest
		oteltest.WithLogging[*testing.T](), // direct t.Log etc. to span logs
	).RunTests(&FruitSuite{})
}

type FruitSuite struct{}

func (FruitSuite) TestApple(ctx context.Context, t *testctx.T) {
    // ...
}

func (FruitSuite) TestBanana(ctx context.Context, t *testctx.T) {
    // ...
}

// Creates the following span tree:
//
//	TestFruits
//	├── TestApple
//	└── TestBanana
//
// All test logs are additionally sent to OpenTelemetry if configured.

Middleware

Middleware are implemented as functions that wrap test functions.

// WithTimeout creates middleware that adds a timeout to the test context
func WithTimeout[T testctx.Runner[T]](d time.Duration) testctx.Middleware[T] {
	return func(next testctx.RunFunc[T]) testctx.RunFunc[T] {
		return func(ctx context.Context, t *testctx.W[T]) {
			ctx, cancel := context.WithTimeout(ctx, d)
			defer cancel()
			next(ctx, t)
		}
	}
}

The types look a little spooky, but it's so that they can work with both *testing.T and *testing.B without extra type assertions.

Differences from Go 1.24 t.Context()

Go 1.24 adds a t.Context() accessor which provides a context.Context that's canceled when the test completes. However, it doesn't provide any way to modify that context.

This package goes further by:

  • Passing context directly to test methods
  • Supporting WithContext() to modify contexts for subtests
  • Adding middleware support for transparent instrumentation

Documentation

Overview

Package testctx provides a context-aware wrapper around testing.T and testing.B with support for middleware and context propagation. It aims to represent what *testing.T might have looked like if context.Context existed at the time it was created.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type B

type B = W[*testing.B]

B is a wrapper around *testing.B

type BenchFunc

type BenchFunc = RunFunc[*testing.B]

BenchFunc is a benchmark function that takes a context and B

type BenchMiddleware added in v0.0.4

type BenchMiddleware = Middleware[*testing.B]

BenchMiddleware is a middleware function that takes a context and B

type Logger added in v0.0.2

type Logger interface {
	Log(args ...any)
	Logf(format string, args ...any)
	Error(args ...any)
	Errorf(format string, args ...any)
}

Logger represents something that can receive test log messages

type Middleware

type Middleware[T Runner[T]] func(RunFunc[T]) RunFunc[T]

Middleware represents a function that can wrap a test function

func WithParallel

func WithParallel() Middleware[*testing.T]

WithParallel creates middleware that runs tests in parallel

func WithTimeout

func WithTimeout[T Runner[T]](d time.Duration) Middleware[T]

WithTimeout creates middleware that adds a timeout to the test context

type MultiLogger added in v0.0.5

type MultiLogger []Logger

MultiLogger multiplexes to a set of loggers.

func (MultiLogger) Error added in v0.0.5

func (ml MultiLogger) Error(args ...any)

Error forwards the error call to all loggers

func (MultiLogger) Errorf added in v0.0.5

func (ml MultiLogger) Errorf(format string, args ...any)

Errorf forwards the formatted error call to all loggers

func (MultiLogger) Log added in v0.0.5

func (ml MultiLogger) Log(args ...any)

Log forwards the log call to all loggers

func (MultiLogger) Logf added in v0.0.5

func (ml MultiLogger) Logf(format string, args ...any)

Logf forwards the formatted log call to all loggers

type RunFunc

type RunFunc[T Runner[T]] func(context.Context, *W[T])

RunFunc represents a test function that takes a context and a wrapper

type Runner

type Runner[T testing.TB] interface {
	testing.TB
	Run(string, func(T)) bool
}

Runner is a constraint for types that support subtests/subbenchmarks

type T

type T = W[*testing.T]

T is a wrapper around *testing.T

type TestFunc

type TestFunc = RunFunc[*testing.T]

TestFunc is a test function that takes a context and T

type TestMiddleware added in v0.0.4

type TestMiddleware = Middleware[*testing.T]

TestMiddleware is a middleware function that takes a context and T

type W

type W[T Runner[T]] struct {

	// we have to embed testing.TB to become a testing.TB ourselves,
	// since it has a private method
	testing.TB
	// contains filtered or unexported fields
}

W is a context-aware wrapper for test/benchmark types that supports middleware and context propagation

func New

func New[T Runner[T]](t T, middleware ...Middleware[T]) *W[T]

New creates a context-aware test wrapper. The wrapper provides:

  • Context propagation through test hierarchies
  • Middleware support for test instrumentation
  • Logging interception via WithLogger

The context is automatically canceled when the test completes. See Using() for details on middleware behavior.

func (*W[T]) BaseName

func (w *W[T]) BaseName() string

BaseName returns the name of the test without the full path prefix

func (*W[T]) Context

func (w *W[T]) Context() context.Context

Context returns the current context

func (*W[T]) Error

func (w *W[T]) Error(args ...any)

Error calls through to the underlying test/benchmark type and logs if a logger is set

func (*W[T]) Errorf

func (w *W[T]) Errorf(format string, args ...any)

Errorf calls through to the underlying test/benchmark type and logs if a logger is set

func (*W[T]) Fatal

func (w *W[T]) Fatal(args ...any)

Fatal calls through to the underlying test/benchmark type and logs if a logger is set

func (*W[T]) Fatalf

func (w *W[T]) Fatalf(format string, args ...any)

Fatalf calls through to the underlying test/benchmark type and logs if a logger is set

func (*W[T]) Log

func (w *W[T]) Log(args ...any)

Log calls through to the underlying test/benchmark type and logs if a logger is set

func (*W[T]) Logf

func (w *W[T]) Logf(format string, args ...any)

Logf calls through to the underlying test/benchmark type and logs if a logger is set

func (*W[T]) Run

func (w *W[T]) Run(name string, fn RunFunc[T]) bool

Run runs a subtest with the given name and function. The function will be wrapped by any middleware registered via Using() or New(), with middleware executing in the order described by Using().

func (*W[T]) RunBenchmarks added in v0.0.4

func (w *W[T]) RunBenchmarks(containers ...any)

RunBenchmarks runs Benchmark* methods from one or more benchmark containers

func (*W[T]) RunTests added in v0.0.4

func (w *W[T]) RunTests(containers ...any)

RunTests runs Test* methods from one or more test containers

func (*W[T]) Skip

func (w *W[T]) Skip(args ...any)

Skip calls through to the underlying test/benchmark type and logs if a logger is set

func (*W[T]) Skipf

func (w *W[T]) Skipf(format string, args ...any)

Skipf calls through to the underlying test/benchmark type and logs if a logger is set

func (*W[T]) Unwrap

func (w *W[T]) Unwrap() T

Unwrap returns the underlying test/benchmark type

func (*W[T]) Using added in v0.0.2

func (w *W[T]) Using(m ...Middleware[T]) *W[T]

Using adds middleware to the wrapper. Middleware are executed in a nested pattern: the first middleware added becomes the outermost wrapper, the last middleware added becomes the innermost wrapper. For example:

t.Using(first, second, third)

Results in the execution order:

first {
    second {
        third {
            test
        }
    }
}

This pattern ensures that: 1. "before" middleware code executes from outside-in (first -> third) 2. The test executes 3. "after" middleware code executes from inside-out (third -> first)

This matches the behavior of other middleware systems like net/http handlers and allows middleware to properly wrap both the setup and cleanup of resources.

func (*W[T]) WithContext

func (w *W[T]) WithContext(ctx context.Context) *W[T]

WithContext creates a new wrapper with the given context

func (*W[T]) WithLogger added in v0.0.2

func (w *W[T]) WithLogger(l Logger) *W[T]

WithLogger returns a new wrapper with the given logger. The logger will receive copies of all test log messages (Log, Logf), errors (Error, Errorf), fatal errors (Fatal, Fatalf), and skip notifications (Skip, Skipf). This allows test output to be captured or redirected while still maintaining the original test behavior.

Directories

Path Synopsis
otelmw module
oteltest module

Jump to

Keyboard shortcuts

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