zoobzio December 13, 2025 Edit this page

Testing

Strategies for testing LLM-powered applications with zyn.

Mock Providers

Fixed Response

func TestClassification(t *testing.T) {
    provider := zyn.NewMockProviderWithResponse(`{
        "primary": "spam",
        "secondary": "",
        "confidence": 0.95,
        "reasoning": ["Contains promotional language"]
    }`)

    classifier, _ := zyn.Classification(
        "Classify email type",
        []string{"spam", "urgent", "personal"},
        provider,
    )

    session := zyn.NewSession()
    result, err := classifier.Fire(context.Background(), session, "Buy now!")

    assert.NoError(t, err)
    assert.Equal(t, "spam", result)
}

Dynamic Response

func TestDynamicResponse(t *testing.T) {
    provider := zyn.NewMockProviderWithCallback(func(prompt string, temp float32) (string, error) {
        if strings.Contains(prompt, "valid") {
            return `{"decision": true, "confidence": 0.9, "reasoning": ["ok"]}`, nil
        }
        return `{"decision": false, "confidence": 0.9, "reasoning": ["invalid"]}`, nil
    })

    validator, _ := zyn.Binary("Is this valid?", provider)
    session := zyn.NewSession()

    result, _ := validator.Fire(context.Background(), session, "valid input")
    assert.True(t, result)
}

Error Simulation

func TestErrorHandling(t *testing.T) {
    provider := zyn.NewMockProviderWithError("rate limit exceeded")

    synapse, _ := zyn.Binary("question", provider)
    session := zyn.NewSession()

    _, err := synapse.Fire(context.Background(), session, "input")
    assert.Error(t, err)
    assert.Contains(t, err.Error(), "rate limit")
}

Testing Package

The testing package provides advanced test utilities:

import zynt "github.com/zoobz-io/zyn/testing"

Response Builder

Build valid responses easily:

// Binary response
response := zynt.NewResponseBuilder().
    WithDecision(true).
    WithConfidence(0.95).
    WithReasoning("Valid format", "Contains required fields").
    Build()

// Classification response
response := zynt.NewResponseBuilder().
    WithPrimary("urgent").
    WithSecondary("").
    WithConfidence(0.87).
    WithReasoning("Time-sensitive language detected").
    Build()

// Ranking response
response := zynt.NewResponseBuilder().
    WithRanked("first", "second", "third").
    WithConfidence(0.9).
    WithReasoning("Ordered by relevance").
    Build()

Sequenced Provider

Return different responses in sequence:

provider := zynt.NewSequencedProvider(
    zynt.NewResponseBuilder().WithDecision(true).WithConfidence(0.9).WithReasoning("first").Build(),
    zynt.NewResponseBuilder().WithDecision(false).WithConfidence(0.8).WithReasoning("second").Build(),
)

synapse, _ := zyn.Binary("question", provider)
session := zyn.NewSession()

result1, _ := synapse.Fire(ctx, session, "input1")  // true
result2, _ := synapse.Fire(ctx, session, "input2")  // false

Failing Provider

Test retry behavior:

// Fails twice, then succeeds
provider := zynt.NewFailingProvider(2).
    WithSuccessResponse(zynt.NewResponseBuilder().
        WithDecision(true).
        WithConfidence(0.9).
        WithReasoning("recovered").
        Build())

synapse, _ := zyn.Binary("question", provider, zyn.WithRetry(3))
session := zyn.NewSession()

result, err := synapse.Fire(ctx, session, "input")
assert.NoError(t, err)  // Succeeds on third attempt
assert.True(t, result)
assert.Equal(t, 3, provider.CallCount())

Call Recorder

Inspect provider calls:

inner := zynt.NewSequencedProvider(`{"decision": true, "confidence": 0.9, "reasoning": ["ok"]}`)
recorder := zynt.NewCallRecorder(inner)

synapse, _ := zyn.Binary("question", recorder)
synapse.Fire(ctx, session, "test input")

calls := recorder.Calls()
assert.Len(t, calls, 1)
assert.Equal(t, "test input", calls[0].Messages[0].Content)

lastCall := recorder.LastCall()
assert.NotNil(t, lastCall)

Latency Provider

Test timeout behavior:

inner := zynt.NewSequencedProvider(`{"decision": true, "confidence": 0.9, "reasoning": ["ok"]}`)
slowProvider := zynt.NewLatencyProvider(inner, 500*time.Millisecond)

synapse, _ := zyn.Binary("question", slowProvider, zyn.WithTimeout(100*time.Millisecond))
session := zyn.NewSession()

_, err := synapse.Fire(ctx, session, "input")
assert.Error(t, err)  // Timeout before provider responds

Testing Patterns

Session State

func TestSessionUpdates(t *testing.T) {
    provider := zyn.NewMockProviderWithResponse(`{...}`)
    synapse, _ := zyn.Binary("question", provider)

    session := zyn.NewSession()
    assert.Equal(t, 0, session.Len())

    synapse.Fire(ctx, session, "input")

    assert.Equal(t, 2, session.Len())  // user + assistant
    assert.Equal(t, zyn.RoleUser, session.Messages()[0].Role)
    assert.Equal(t, zyn.RoleAssistant, session.Messages()[1].Role)
}

Transactional Behavior

func TestSessionUnchangedOnError(t *testing.T) {
    provider := zyn.NewMockProviderWithError("failure")
    synapse, _ := zyn.Binary("question", provider, zyn.WithRetry(2))

    session := zyn.NewSession()
    initialLen := session.Len()

    _, err := synapse.Fire(ctx, session, "input")

    assert.Error(t, err)
    assert.Equal(t, initialLen, session.Len())  // Unchanged
}

Custom Type Validation

type Order struct {
    ID     string  `json:"id"`
    Amount float64 `json:"amount"`
}

func (o Order) Validate() error {
    if o.ID == "" {
        return fmt.Errorf("ID required")
    }
    return nil
}

func TestValidationError(t *testing.T) {
    // Response missing required ID
    provider := zyn.NewMockProviderWithResponse(`{
        "id": "",
        "amount": 100,
        "confidence": 0.9,
        "reasoning": ["extracted"]
    }`)

    extractor, _ := zyn.Extract[Order]("extract order", provider)
    session := zyn.NewSession()

    _, err := extractor.Fire(ctx, session, "Order for $100")
    assert.Error(t, err)
    assert.Contains(t, err.Error(), "ID required")
}

Integration Tests

For tests against real providers:

func TestRealProvider(t *testing.T) {
    if os.Getenv("OPENAI_API_KEY") == "" {
        t.Skip("OPENAI_API_KEY not set")
    }

    provider := openai.New(openai.Config{
        APIKey: os.Getenv("OPENAI_API_KEY"),
        Model:  "gpt-4o-mini",
    })

    synapse, _ := zyn.Binary("Is this a valid email?", provider,
        zyn.WithTimeout(30*time.Second),
    )

    session := zyn.NewSession()
    result, err := synapse.Fire(context.Background(), session, "test@example.com")

    assert.NoError(t, err)
    assert.True(t, result)
}

Benchmarks

func BenchmarkSynapseFire(b *testing.B) {
    provider := zynt.NewSequencedProvider(
        zynt.NewResponseBuilder().
            WithDecision(true).
            WithConfidence(0.9).
            WithReasoning("ok").
            Build(),
    )

    synapse, _ := zyn.Binary("question", provider)

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        session := zyn.NewSession()
        synapse.Fire(context.Background(), session, "input")
    }
}

Next Steps