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.
- 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
go get github.com/rios0rios0/testkitpackage 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)
}
}// 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()
})// 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)
}type Product struct {
ID int
Name string
Price float64
Category string
InStock bool
Tags map[string]string
}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
}func init() {
testkit.RegisterBuilder("product", func() testkit.Builder {
return NewProductBuilder()
})
}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)
}
}// 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 statebuilder := 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"))
}Builder: Main interface all builders must implementConfigurableBuilder: Optional interface for configuration support
BaseBuilder: Common functionality for all buildersBuilderFactory: Factory for creating and managing buildersBuilderConfig: Configuration container for builders
UserBuilder: Complete example of a custom builderTestUser: Example entity for testing
Contributions are welcome. See CONTRIBUTING.md for guidelines.
This project is licensed under the MIT License. See the LICENSE file for details.