README
¶
testkit
Go testing utility library offering a modular builder framework to streamline test environment setup. It provides interfaces, ready-to-use structs, and patterns for creating reusable builders, fixtures, and mocks. Extensible and ideal for consistent, maintainable tests across repos.
Features
- Modular Builder Pattern: Extensible base builder with common functionality
- Factory System: Register and create builders dynamically
- Configuration Management: Apply default values and settings to builders
- Validation Framework: Built-in validation with error accumulation
- Tag System: Metadata support for builders
- Clone & Reset: Deep copy and state management capabilities
Installation
go get github.com/rios0rios0/testkit
Quick Start
Basic Builder Usage
package main
import (
"fmt"
"github.com/rios0rios0/testkit"
)
func main() {
// Create a base builder
builder := testkit.NewBaseBuilder()
builder.WithTag("env", "test").WithValidation(true)
// Use the pre-built UserBuilder example
userBuilder := testkit.NewUserBuilder()
userBuilder.WithName("John Doe").
WithEmail("john@example.com").
WithAge(30).
WithActive(true)
result := userBuilder.Build()
if user, ok := result.(*testkit.TestUser); ok {
fmt.Printf("Created user: %+v\n", user)
}
}
Factory Pattern
// Register a custom builder
testkit.RegisterBuilder("mybuilder", func() testkit.Builder {
return testkit.NewUserBuilder()
})
// Create from factory
builder, err := testkit.CreateBuilder("mybuilder")
if err != nil {
panic(err)
}
// Or use a custom factory
factory := testkit.NewBuilderFactory()
factory.Register("custom", func() testkit.Builder {
return testkit.NewBaseBuilder()
})
Configuration System
// Create configuration with defaults
config := testkit.NewBuilderConfig()
config.WithValidation(false).
WithTag("env", "test").
WithDefault("name", "Default Name").
WithDefault("age", 25)
// Apply to any builder
builder := testkit.NewUserBuilder()
err := config.ApplyTo(builder)
if err != nil {
panic(err)
}
Creating Custom Builders
Step 1: Define Your Entity
type Product struct {
ID int
Name string
Price float64
Category string
InStock bool
Tags map[string]string
}
Step 2: Create the Builder
type ProductBuilder struct {
*testkit.BaseBuilder
product *Product
}
func NewProductBuilder() *ProductBuilder {
return &ProductBuilder{
BaseBuilder: testkit.NewBaseBuilder(),
product: &Product{
Tags: make(map[string]string),
},
}
}
func (b *ProductBuilder) WithID(id int) *ProductBuilder {
if b.IsValidationEnabled() && id <= 0 {
b.AddError(errors.New("product ID must be positive"))
return b
}
b.product.ID = id
return b
}
func (b *ProductBuilder) WithName(name string) *ProductBuilder {
if b.IsValidationEnabled() && name == "" {
b.AddError(errors.New("product name cannot be empty"))
return b
}
b.product.Name = name
return b
}
func (b *ProductBuilder) WithPrice(price float64) *ProductBuilder {
if b.IsValidationEnabled() && price < 0 {
b.AddError(errors.New("product price cannot be negative"))
return b
}
b.product.Price = price
return b
}
func (b *ProductBuilder) Build() interface{} {
if b.HasErrors() {
return fmt.Errorf("validation errors: %v", b.GetErrors())
}
// Return a copy to avoid mutation
return &Product{
ID: b.product.ID,
Name: b.product.Name,
Price: b.product.Price,
Category: b.product.Category,
InStock: b.product.InStock,
Tags: copyMap(b.product.Tags),
}
}
func (b *ProductBuilder) Reset() testkit.Builder {
b.BaseBuilder.Reset()
b.product = &Product{Tags: make(map[string]string)}
return b
}
func (b *ProductBuilder) Clone() testkit.Builder {
clone := &ProductBuilder{
BaseBuilder: b.BaseBuilder.Clone().(*testkit.BaseBuilder),
product: &Product{
ID: b.product.ID,
Name: b.product.Name,
Price: b.product.Price,
Category: b.product.Category,
InStock: b.product.InStock,
Tags: copyMap(b.product.Tags),
},
}
return clone
}
func copyMap(m map[string]string) map[string]string {
result := make(map[string]string)
for k, v := range m {
result[k] = v
}
return result
}
Step 3: Register with Factory (Optional)
func init() {
testkit.RegisterBuilder("product", func() testkit.Builder {
return NewProductBuilder()
})
}
Advanced Features
Error Handling
builder := NewProductBuilder()
builder.WithID(-1).WithName("") // Both will add validation errors
result := builder.Build()
if err, ok := result.(error); ok {
fmt.Printf("Build failed: %v\n", err)
}
// Check errors during building
if builder.HasErrors() {
for _, err := range builder.GetErrors() {
fmt.Printf("Error: %v\n", err)
}
}
Builder State Management
// Clone for independent copies
original := NewProductBuilder().WithName("Original")
clone := original.Clone().(*ProductBuilder)
clone.WithName("Clone") // Doesn't affect original
// Reset for reuse
builder := NewProductBuilder().WithName("First")
builder.Reset()
builder.WithName("Second") // Fresh state
Metadata and Tags
builder := NewProductBuilder()
builder.WithTag("test_type", "integration").
WithTag("owner", "team_a")
if builder.HasTag("test_type") {
fmt.Printf("Test type: %s\n", builder.GetTag("test_type"))
}
API Reference
Core Interfaces
Builder: Main interface all builders must implementConfigurableBuilder: Optional interface for configuration support
Main Types
BaseBuilder: Common functionality for all buildersBuilderFactory: Factory for creating and managing buildersBuilderConfig: Configuration container for builders
Example Builders
UserBuilder: Complete example of a custom builderTestUser: Example entity for testing
Contributing
Contributions are welcome. See CONTRIBUTING.md for guidelines.
License
This project is licensed under the MIT License. See the LICENSE file for details.
Click to show internal directories.
Click to hide internal directories.