zoobzio January 24, 2026 Edit this page

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