Skip to content
brokle-testing logo

Brokle Testing Skill

brokle-testing

Use this skill when writing, reviewing, or improving tests for the Brokle platform. This includes creating service tests, domain tests, integration tests, or understanding what to test versus what to skip based on the pragmatic testing philosophy.

SKILL.md

Full skill instructions

Brokle Testing Skill

Expert guidance for writing pragmatic, high-value tests following Brokle's testing philosophy.

Testing Philosophy

Core Principle: Test Business Logic, Not Framework Behavior

What We Test ✅

  1. Complex Business Logic: Calculations, retry mechanisms, state machines
  2. Batch Operations: Bulk processing, partial success/​failure handling
  3. Multi-Step Orchestration: Multiple repository calls, cross-service workflows
  4. Error Handling Patterns: Domain error mapping, retry logic with backoff
  5. Analytics & Aggregations: Time-based queries, statistical calculations

What We Don't Test ❌

  1. Simple CRUD: Basic Create/​Read/​Update/​Delete with no business logic
  2. Field Validation: Already tested in domain layer
  3. Trivial Constructors: Simple object creation
  4. Framework Behavior: ULID generation, time.Now(), errors.Is (stdlib)
  5. Static Definitions: Constant strings, enum type checkers

Coverage Guidelines

Target Metrics:

  • Service Layer: ~1:1 test-to-code ratio (focus on business logic)
  • Domain Layer: Minimal (only complex calculations)
  • Handler Layer: Critical workflows only

Acceptable Ratios:

  • ✅ 0.8:1 to 1.2:1 - Healthy coverage
  • ⚠️ < 0.5:1 - Missing critical coverage
  • ⚠️ > 2:1 - Testing too many trivial operations

Service Layer Test Pattern

// ============================================================================
// Mock Repositories (Full Interface Implementation)
// ============================================================================

type MockUserRepository struct {
    mock.Mock
}

func (m *MockUserRepository) Create(ctx context.Context, user *authDomain.User) error {
    args := m.Called(ctx, user)
    return args.Error(0)
}

func (m *MockUserRepository) GetByID(ctx context.Context, id ulid.ULID) (*authDomain.User, error) {
    args := m.Called(ctx, id)
    if args.Get(0) == nil {
        return nil, args.Error(1)
    }
    return args.Get(0).(*authDomain.User), args.Error(1)
}

// Implement ALL interface methods (even if not used in test)

// ============================================================================
// HIGH-VALUE TESTS: Complex Business Logic
// ============================================================================

func TestUserService_CreateUser(t *testing.T) {
    tests := []struct {
        name        string
        input       *CreateUserRequest
        mockSetup   func(*MockUserRepository)
        expectedErr error
        checkResult func(*testing.T, *CreateUserResponse)
    }{
        {
            name: "success - valid user",
            input: &CreateUserRequest{
                Email: "[email protected]",
                Name:  "Test User",
            },
            mockSetup: func(repo *MockUserRepository) {
                // Expect email check (not found)
                repo.On("GetByEmail", mock.Anything, "[email protected]").
                    Return(nil, authDomain.ErrNotFound)

                // Expect user creation
                repo.On("Create", mock.Anything, mock.MatchedBy(func(user *authDomain.User) bool {
                    return user.Email == "[email protected]" && user.Name == "Test User"
                })).Return(nil)
            },
            expectedErr: nil,
            checkResult: func(t *testing.T, resp *CreateUserResponse) {
                assert.NotNil(t, resp)
                assert.NotEqual(t, ulid.ULID{}, resp.User.ID)
                assert.Equal(t, "[email protected]", resp.User.Email)
            },
        },
        {
            name: "error - user already exists",
            input: &CreateUserRequest{
                Email: "[email protected]",
                Name:  "Existing User",
            },
            mockSetup: func(repo *MockUserRepository) {
                // Return existing user
                existingUser := &authDomain.User{
                    ID:    ulid.New(),
                    Email: "[email protected]",
                }
                repo.On("GetByEmail", mock.Anything, "[email protected]").
                    Return(existingUser, nil)

                // No create call expected
            },
            expectedErr: errors.New("conflict"),  // Check error message, not sentinel
            checkResult: nil,
        },
        {
            name: "error - repository failure",
            input: &CreateUserRequest{
                Email: "[email protected]",
                Name:  "Test User",
            },
            mockSetup: func(repo *MockUserRepository) {
                repo.On("GetByEmail", mock.Anything, "[email protected]").
                    Return(nil, authDomain.ErrNotFound)
                repo.On("Create", mock.Anything, mock.Anything).
                    Return(fmt.Errorf("database error"))
            },
            expectedErr: errors.New("internal"),  // Check error message, not sentinel
            checkResult: nil,
        },
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            // Setup mocks
            mockRepo := new(MockUserRepository)
            tt.mockSetup(mockRepo)

            // Create service
            service := NewUserService(mockRepo)

            // Execute
            result, err := service.CreateUser(context.Background(), tt.input)

            // Assert errors
            if tt.expectedErr != nil {
                assert.Error(t, err)
                assert.ErrorIs(t, err, tt.expectedErr)
            } else {
                assert.NoError(t, err)
            }

            // Assert results
            if tt.checkResult != nil {
                tt.checkResult(t, result)
            }

            // Verify all mock expectations were met
            mockRepo.AssertExpectations(t)
        })
    }
}

Key Testing Patterns

1. Table-Driven Tests

tests := []struct {
    name        string
    input       InputType
    mockSetup   func(*MockRepo)
    expectedErr error
    checkResult func(*testing.T, *Result)
}{
    {
        name: "success case",
        input: validInput,
        mockSetup: func(repo *MockRepo) {
            repo.On("Method", mock.Anything, mock.Anything).Return(nil)
        },
        expectedErr: nil,
        checkResult: func(t *testing.T, result *Result) {
            assert.NotNil(t, result)
        },
    },
}

2. Complete Mock Interface Implementation

type MockUserRepository struct {
    mock.Mock
}

// Implement ALL methods from interface
func (m *MockUserRepository) Create(ctx context.Context, user *User) error {
    args := m.Called(ctx, user)
    return args.Error(0)
}

func (m *MockUserRepository) GetByID(ctx context.Context, id ulid.ULID) (*User, error) {
    args := m.Called(ctx, id)
    if args.Get(0) == nil {
        return nil, args.Error(1)
    }
    return args.Get(0).(*User), args.Error(1)
}

// Implement all other methods even if unused

3. Mock Setup Functions

mockSetup: func(userRepo *MockUserRepo, orgRepo *MockOrgRepo) {
    // Use mock.MatchedBy for complex validation
    userRepo.On("Create", mock.Anything, mock.MatchedBy(func(user *User) bool {
        return user.Email != "" && user.Name != ""
    })).Return(nil)

    // Multiple expectations in order
    orgRepo.On("GetByID", mock.Anything, orgID).Return(org, nil)
    orgRepo.On("Update", mock.Anything, mock.Anything).Return(nil)
}

4. Result Validation Functions

checkResult: func(t *testing.T, result *CreateUserResponse) {
    assert.NotNil(t, result)
    assert.NotEqual(t, ulid.ULID{}, result.User.ID)
    assert.Equal(t, "[email protected]", result.User.Email)
    assert.NotNil(t, result.User.CreatedAt)
    assert.Equal(t, "active", result.User.Status)
}

5. Mock Expectation Verification

// ALWAYS call AssertExpectations on all mocks
mockUserRepo.AssertExpectations(t)
mockOrgRepo.AssertExpectations(t)
mockPublisher.AssertExpectations(t)

Domain Layer Tests

Only test complex business logic:

// ✅ HIGH-VALUE: Business logic calculation
func TestSpan_CalculateLatency(t *testing.T) {
    startTime := time.Now()
    endTime := startTime.Add(150 * time.Millisecond)

    tests := []struct {
        name     string
        span      *Span
        expected *int
    }{
        {
            name: "with valid end time",
            span: &Span{
                StartTime: startTime,
                EndTime:   &endTime,
            },
            expected: func() *int { val := 150; return &val }(),
        },
        {
            name: "without end time",
            span: &Span{
                StartTime: startTime,
                EndTime:   nil,
            },
            expected: nil,
        },
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result := tt.obs.CalculateLatency()
            if tt.expected == nil {
                assert.Nil(t, result)
            } else {
                assert.NotNil(t, result)
                assert.Equal(t, *tt.expected, *result)
            }
        })
    }
}

Integration Tests

func TestUserService_Integration(t *testing.T) {
    // Skip if not running integration tests
    if testing.Short() {
        t.Skip("Skipping integration test")
    }

    // Setup test database
    db := setupTestDB(t)
    defer cleanupTestDB(t, db)

    // Setup real dependencies
    userRepo := repository.NewUserRepository(db)
    service := services.NewUserService(userRepo)

    // Test complete workflow
    user := &CreateUserRequest{
        Email: "[email protected]",
        Name:  "Integration Test",
    }

    result, err := service.CreateUser(context.Background(), user)
    require.NoError(t, err)
    require.NotEqual(t, ulid.ULID{}, result.User.ID)

    // Verify persistence
    retrieved, err := service.GetUser(context.Background(), result.User.ID)
    require.NoError(t, err)
    assert.Equal(t, result.User.Email, retrieved.User.Email)
}

Running Tests

# All tests
make test

# Unit tests only (skip integration)
make test-unit

# Integration tests with real databases
make test-integration

# With coverage
make test-coverage

# Specific package
go test ./​internal/​core/​services/​observability -v

# Specific test
go test ./​internal/​core/​services/​observability -run TestUserService_Create -v

# With race detection
go test -race ./​...

# Skip integration tests
go test -short ./​...

Test Quality Checklist

Before Writing Tests:

  • Identify business logic (not CRUD)
  • Check if complex enough to warrant testing
  • Review similar tests in internal/​core/​services/​observability/​*_test.go
  • Plan test scenarios (success, validation errors, repository errors)

Test Implementation:

  • Table-driven test pattern
  • Complete mock interface implementations
  • Mock expectations verified with AssertExpectations(t)
  • Result validation functions for complex assertions
  • Clear test names describing scenarios
  • ~1:1 test-to-code ratio for business logic

What NOT to Test:

  • ❌ Simple CRUD operations
  • ❌ Field validation (domain layer)
  • ❌ Trivial constructors
  • ❌ Framework behavior
  • ❌ Static constants

References

  • docs/​TESTING.md - Complete testing guide
  • internal/​core/​services/​observability/​*_test.go - Reference implementations
  • prompts/​testing.txt - AI-assisted test generation

More skills from majiayu000

xiaohongshu logo
majiayu000/claude-arsenal

xiaohongshu

xiaohongshu

286 148
View
agent-task-conductor logo
majiayu000/claude-skill-registry

agent-task-conductor

Conduct multi-agent task orchestration and workflow coordination.

663 1
View
conductor-setup logo
majiayu000/claude-skill-registry

conductor-setup

Initialize project with Conductor artifacts (product definition,

663 1
View
animation-designer logo
majiayu000/claude-skill-registry

animation-designer

Expert in web animations, transitions, and motion design using Framer Motion and CSS

663 1
View
diagramming logo
majiayu000/claude-skill-registry

diagramming

Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...

663 1
View
h3-pg logo
majiayu000/claude-skill-registry-data

h3-pg

PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.

23 1
View
conductor-development logo
majiayu000/claude-skill-registry

conductor-development

Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...

663 1
View
conductor-status logo
majiayu000/claude-skill-registry

conductor-status

Display project status, active tracks, and next actions

663 1
View
dockerization logo
majiayu000/claude-skill-registry

dockerization

Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.

663 1
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View