Testing
This guide covers testing code that uses slush for service lookup.
Test Isolation
slush uses a global registry. Tests must reset state to avoid interference:
func TestMyHandler(t *testing.T) {
slush.Reset()
defer slush.Reset()
key := slush.Start()
slush.Register[Database](key, mockDB{})
// Run test...
}
Using the testing helper:
slush provides a helper that resets, starts, and registers cleanup:
import slushtesting "github.com/zoobzio/slush/testing"
func TestMyHandler(t *testing.T) {
key := slushtesting.StartRegistry(t) // Resets, starts, and registers cleanup
slush.Register[Database](key, mockDB{})
// ...
}
Mocking Services
Create mock implementations of your service interfaces:
type mockDatabase struct {
queryFunc func(ctx context.Context, sql string) ([]Row, error)
}
func (m mockDatabase) Query(ctx context.Context, sql string) ([]Row, error) {
if m.queryFunc != nil {
return m.queryFunc(ctx, sql)
}
return nil, nil
}
func TestQueryHandler(t *testing.T) {
key := slushtesting.StartRegistry(t)
// Configure mock behavior
slush.Register[Database](key, mockDatabase{
queryFunc: func(ctx context.Context, sql string) ([]Row, error) {
if sql == "SELECT * FROM users" {
return []Row{{ID: 1, Name: "Alice"}}, nil
}
return nil, errors.New("unexpected query")
},
})
// Test the handler
result := handleQuery(context.Background(), "SELECT * FROM users")
// Assert...
}
Testing Guards
Test that guards are invoked
func TestGuardIsInvoked(t *testing.T) {
key := slushtesting.StartRegistry(t)
var guardCalled bool
slush.Register[Database](key, mockDB{}).
Guard(func(ctx context.Context) error {
guardCalled = true
return nil
})
_, _ = slush.Use[Database](context.Background())
if !guardCalled {
t.Error("guard was not invoked")
}
}
Test guard rejection
func TestGuardRejectsUnauthorized(t *testing.T) {
key := slushtesting.StartRegistry(t)
slush.Register[Database](key, mockDB{}).
Guard(requireAuth)
// Context without auth
ctx := context.Background()
_, err := slush.Use[Database](ctx)
if !errors.Is(err, slush.ErrAccessDenied) {
t.Errorf("expected ErrAccessDenied, got %v", err)
}
}
Test guard passes with valid context
func TestGuardAllowsAuthorized(t *testing.T) {
key := slushtesting.StartRegistry(t)
slush.Register[Database](key, mockDB{}).
Guard(requireAuth)
// Context with auth
ctx := auth.WithUser(context.Background(), &User{ID: 1})
db, err := slush.Use[Database](ctx)
if err != nil {
t.Errorf("expected access, got %v", err)
}
if db == nil {
t.Error("expected service, got nil")
}
}
Test Helpers
slush's testing package provides common guard helpers:
import slushtesting "github.com/zoobzio/slush/testing"
func TestWithAllowAllGuard(t *testing.T) {
key := slushtesting.StartRegistry(t)
slush.Register[Database](key, mockDB{}).
Guard(slushtesting.AllowAllGuard())
// Access always succeeds
_, err := slush.Use[Database](context.Background())
if err != nil {
t.Error("AllowAllGuard should permit access")
}
}
func TestWithDenyAllGuard(t *testing.T) {
key := slushtesting.StartRegistry(t)
slush.Register[Database](key, mockDB{}).
Guard(slushtesting.DenyAllGuard())
// Access always fails
_, err := slush.Use[Database](context.Background())
if !errors.Is(err, slush.ErrAccessDenied) {
t.Error("DenyAllGuard should deny access")
}
}
func TestGuardInvocationCount(t *testing.T) {
key := slushtesting.StartRegistry(t)
var count int
slush.Register[Database](key, mockDB{}).
Guard(slushtesting.CountingGuard(&count))
_, _ = slush.Use[Database](context.Background())
_, _ = slush.Use[Database](context.Background())
if count != 2 {
t.Errorf("expected 2 invocations, got %d", count)
}
}
Testing Service Enumeration
func TestServicesReturnsRegistered(t *testing.T) {
key := slushtesting.StartRegistry(t)
slush.Register[Database](key, mockDB{})
slush.Register[Mailer](key, mockMailer{})
svcs, err := slush.Services(key)
if err != nil {
t.Fatalf("Services error: %v", err)
}
if len(svcs) != 2 {
t.Errorf("expected 2 services, got %d", len(svcs))
}
}
Table-Driven Tests
For comprehensive guard testing:
func TestAccessControl(t *testing.T) {
tests := []struct {
name string
ctx context.Context
wantErr error
}{
{
name: "anonymous user denied",
ctx: context.Background(),
wantErr: slush.ErrAccessDenied,
},
{
name: "regular user denied",
ctx: auth.WithUser(context.Background(), &User{Role: "user"}),
wantErr: slush.ErrAccessDenied,
},
{
name: "admin user allowed",
ctx: auth.WithUser(context.Background(), &User{Role: "admin"}),
wantErr: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
key := slushtesting.StartRegistry(t)
slush.Register[AdminService](key, mockAdminService{}).
Guard(requireRole("admin"))
_, err := slush.Use[AdminService](tt.ctx)
if tt.wantErr == nil && err != nil {
t.Errorf("expected no error, got %v", err)
}
if tt.wantErr != nil && !errors.Is(err, tt.wantErr) {
t.Errorf("expected %v, got %v", tt.wantErr, err)
}
})
}
}
Next Steps
- Troubleshooting — Debug common issues
- Guards Guide — Write testable guards