testkit

package
v0.3.1 Latest Latest
Warning

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

Go to latest
Published: Aug 29, 2026 License: MIT Imports: 5 Imported by: 3

Documentation

Overview

Package testkit provides utilities for testing setups including a modular builder framework.

The testkit library offers a comprehensive set of tools for creating consistent, maintainable test environments. It provides interfaces, ready-to-use structs, and patterns for creating reusable builders, fixtures, and mocks.

Core Components

The library is built around three main components:

1. Builder Interface and BaseBuilder: A flexible builder pattern implementation 2. BuilderFactory: Factory pattern for creating and managing different builder types 3. BuilderConfig: Configuration system for setting up builders with default values

Basic Usage

Creating a simple builder:

builder := NewBaseBuilder()
builder.WithTag("env", "test").WithValidation(true)
result := builder.Build() // Returns nil for base builder

Using the factory pattern:

factory := NewBuilderFactory()
factory.Register("mybuilder", func() Builder {
	return NewBaseBuilder()
})

builder, err := factory.Create("mybuilder")
if err != nil {
	// handle error
}

Creating Custom Builders

To create a custom builder, embed BaseBuilder and implement the Builder interface:

type MyObjectBuilder struct {
	*BaseBuilder
	obj *MyObject
}

func NewMyObjectBuilder() *MyObjectBuilder {
	return &MyObjectBuilder{
		BaseBuilder: NewBaseBuilder(),
		obj:         &MyObject{},
	}
}

func (b *MyObjectBuilder) WithName(name string) *MyObjectBuilder {
	if b.IsValidationEnabled() && name == "" {
		b.AddError(errors.New("name cannot be empty"))
		return b
	}
	b.obj.Name = name
	return b
}

func (b *MyObjectBuilder) Build() interface{} {
	if b.HasErrors() {
		return fmt.Errorf("validation errors: %v", b.GetErrors())
	}
	// Return a copy to avoid mutation
	return &MyObject{Name: b.obj.Name}
}

Configuration System

Use BuilderConfig for setting up builders with defaults:

config := NewBuilderConfig()
config.WithValidation(false)
config.WithTag("env", "test")
config.WithDefault("name", "default_name")

builder := NewMyObjectBuilder()
config.ApplyTo(builder)

Thread Safety

Individual builders are not thread-safe by design. Each goroutine should use its own builder instance. Use Clone() to create independent copies when needed.

BuilderFactory (and therefore the package-level DefaultFactory with its RegisterBuilder and CreateBuilder helpers) is safe for concurrent use: parallel tests and their helper goroutines may register and create builders concurrently. The instances a factory hands out remain subject to the rule above.

Error Handling

Builders accumulate errors during configuration and report them at build time:

builder := NewMyObjectBuilder()
builder.WithName("") // Adds validation error

result := builder.Build()
if err, ok := result.(error); ok {
	// Handle build errors
}

Index

Constants

This section is empty.

Variables

View Source
var DefaultFactory = NewBuilderFactory() //nolint:gochecknoglobals // intentional singleton for convenience API

DefaultFactory is a global factory instance for convenience.

Functions

func RegisterBuilder

func RegisterBuilder(name string, createFunc func() Builder) error

RegisterBuilder registers a builder in the default factory.

Types

type BaseBuilder

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

BaseBuilder provides common functionality for all builders. It implements the Builder interface and can be embedded in specific builders.

func NewBaseBuilder

func NewBaseBuilder() *BaseBuilder

NewBaseBuilder creates a new BaseBuilder instance with default settings.

func (*BaseBuilder) AddError

func (b *BaseBuilder) AddError(err error) *BaseBuilder

AddError adds an error to the builder's error collection.

func (*BaseBuilder) Build

func (b *BaseBuilder) Build() any

Build is a default implementation that returns nil. Specific builders should override this method.

func (*BaseBuilder) ClearErrors

func (b *BaseBuilder) ClearErrors() *BaseBuilder

ClearErrors removes all errors from the builder.

func (*BaseBuilder) Clone

func (b *BaseBuilder) Clone() Builder

Clone creates a deep copy of the BaseBuilder.

func (*BaseBuilder) GetErrors

func (b *BaseBuilder) GetErrors() []error

GetErrors returns all errors accumulated by the builder.

func (*BaseBuilder) GetTag

func (b *BaseBuilder) GetTag(key string) string

GetTag retrieves a metadata tag value by key. Returns empty string if the tag doesn't exist.

func (*BaseBuilder) HasErrors

func (b *BaseBuilder) HasErrors() bool

HasErrors returns true if the builder has any errors.

func (*BaseBuilder) HasTag

func (b *BaseBuilder) HasTag(key string) bool

HasTag checks if a metadata tag exists.

func (*BaseBuilder) IsValidationEnabled

func (b *BaseBuilder) IsValidationEnabled() bool

IsValidationEnabled returns whether validation is enabled for this builder.

func (*BaseBuilder) Reset

func (b *BaseBuilder) Reset() Builder

Reset clears the builder state, allowing it to be reused.

func (*BaseBuilder) WithTag

func (b *BaseBuilder) WithTag(key, value string) *BaseBuilder

WithTag adds a metadata tag to the builder. Tags can be used for identification, debugging, or conditional logic.

func (*BaseBuilder) WithValidation

func (b *BaseBuilder) WithValidation(enabled bool) *BaseBuilder

WithValidation enables or disables validation for this builder.

type Builder

type Builder interface {
	// Build performs the final construction step and returns the built object.
	// Implementations should return the appropriate type for their specific builder.
	Build() any

	// Reset clears the builder state, allowing it to be reused.
	Reset() Builder

	// Clone creates a deep copy of the builder with the same configuration.
	Clone() Builder
}

Builder defines the interface that all builders must implement. This provides a common contract for all test builders in the library.

func CreateBuilder

func CreateBuilder(name string) (Builder, error)

CreateBuilder creates a builder from the default factory.

type BuilderConfig

type BuilderConfig struct {
	ValidationEnabled bool
	Tags              map[string]string
	DefaultValues     map[string]any
}

BuilderConfig provides configuration options for builders.

func NewBuilderConfig

func NewBuilderConfig() *BuilderConfig

NewBuilderConfig creates a new BuilderConfig with default settings.

func (*BuilderConfig) ApplyTo

func (c *BuilderConfig) ApplyTo(builder Builder) error

ApplyTo applies the configuration to a builder.

func (*BuilderConfig) WithDefault

func (c *BuilderConfig) WithDefault(key string, value any) *BuilderConfig

WithDefault sets a default value for a field.

func (*BuilderConfig) WithTag

func (c *BuilderConfig) WithTag(key, value string) *BuilderConfig

WithTag adds a tag to the configuration.

func (*BuilderConfig) WithValidation

func (c *BuilderConfig) WithValidation(enabled bool) *BuilderConfig

WithValidation sets the validation enabled flag.

type BuilderFactory

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

BuilderFactory provides a way to register and create different types of builders.

A BuilderFactory is safe for concurrent use. That matters more here than it would in ordinary library code: DefaultFactory is a package-level singleton, so every t.Parallel() test in a package -- and any helper goroutine those tests start -- reaches the same map through RegisterBuilder and CreateBuilder. (Separate packages run as separate test binaries and do not share it; the race is inside one binary, not across them.) An unsynchronised concurrent map write is a runtime THROW, not a panic -- `recover` cannot catch it, so it takes the whole test binary down with "fatal error: concurrent map writes" and no failing test to point at.

func NewBuilderFactory

func NewBuilderFactory() *BuilderFactory

NewBuilderFactory creates a new BuilderFactory instance.

func (*BuilderFactory) Create

func (f *BuilderFactory) Create(name string) (Builder, error)

Create creates a new builder instance by name.

func (*BuilderFactory) GetRegisteredNames

func (f *BuilderFactory) GetRegisteredNames() []string

GetRegisteredNames returns all registered builder names.

func (*BuilderFactory) IsRegistered

func (f *BuilderFactory) IsRegistered(name string) bool

IsRegistered checks if a builder is registered with the given name.

func (*BuilderFactory) Register

func (f *BuilderFactory) Register(name string, createFunc func() Builder) error

Register registers a builder creation function with a given name.

type ConfigurableBuilder

type ConfigurableBuilder interface {
	Builder
	ApplyConfig(config *BuilderConfig) error
}

ConfigurableBuilder interface for builders that can accept configuration.

type TestUser

type TestUser struct {
	ID       int
	Name     string
	Email    string
	Age      int
	Active   bool
	Tags     map[string]string
	Metadata map[string]any
}

TestUser represents a test user entity for demonstration purposes.

type UserBuilder

type UserBuilder struct {
	*BaseBuilder
	// contains filtered or unexported fields
}

UserBuilder builds TestUser instances for testing. It embeds BaseBuilder to inherit common functionality.

func NewUserBuilder

func NewUserBuilder() *UserBuilder

NewUserBuilder creates a new UserBuilder instance.

func (*UserBuilder) ApplyConfig

func (b *UserBuilder) ApplyConfig(config *BuilderConfig) error

ApplyConfig implements ConfigurableBuilder interface.

func (*UserBuilder) Build

func (b *UserBuilder) Build() any

Build creates the TestUser instance. It performs final validation and returns the user or an error.

func (*UserBuilder) Clone

func (b *UserBuilder) Clone() Builder

Clone creates a deep copy of the UserBuilder.

func (*UserBuilder) Reset

func (b *UserBuilder) Reset() Builder

Reset clears the builder state for reuse.

func (*UserBuilder) WithActive

func (b *UserBuilder) WithActive(active bool) *UserBuilder

WithActive sets the user active status.

func (*UserBuilder) WithAge

func (b *UserBuilder) WithAge(age int) *UserBuilder

WithAge sets the user age.

func (*UserBuilder) WithEmail

func (b *UserBuilder) WithEmail(email string) *UserBuilder

WithEmail sets the user email.

func (*UserBuilder) WithID

func (b *UserBuilder) WithID(id int) *UserBuilder

WithID sets the user ID.

func (*UserBuilder) WithMetadata

func (b *UserBuilder) WithMetadata(key string, value any) *UserBuilder

WithMetadata adds metadata to the user.

func (*UserBuilder) WithName

func (b *UserBuilder) WithName(name string) *UserBuilder

WithName sets the user name.

func (*UserBuilder) WithUserTag

func (b *UserBuilder) WithUserTag(key, value string) *UserBuilder

WithUserTag adds a tag specific to the user entity.

Jump to

Keyboard shortcuts

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