Skip to Content
GuidesTesting & Debugging

Testing & Debugging Guide

For: Developers testing workflows and activities
Level: Intermediate → Advanced
Time to read: 30 minutes
Examples: 15+ test patterns

This guide covers testing strategies for workflows, activities, and policies, plus debugging techniques for production issues.


Testing Pyramid

╱ ╲ ╱ E2E ╲ 5-10 tests ╱───────╲ Complete workflows ╱ ╲ ╱ Integration ╲ 30-50 tests ╱─────────────╲ Components together ╱ ╲ ╱ Unit Tests ╲ 100-200 tests ╱─────────────────╲ Isolated functions

Test Types

TypeCoverageSpeedCost
Unit80%FastLow
Integration15%MediumMedium
E2E5%SlowHigh

Unit Testing Activities

Stateless Activity Test

package activities_test import ( "context" "testing" "github.com/stretchr/testify/assert" "myapp/activities" ) func TestTransformText(t *testing.T) { tests := []struct { name string input activities.TransformInput wantOut string wantErr bool }{ { name: "uppercase", input: activities.TransformInput{Text: "hello", Op: "upper"}, wantOut: "HELLO", }, { name: "invalid operation", input: activities.TransformInput{Text: "hello", Op: "invalid"}, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result, err := activities.TransformText(context.Background(), &tt.input) if tt.wantErr { assert.Error(t, err) return } assert.NoError(t, err) assert.Equal(t, tt.wantOut, result.Result) }) } }

Database Activity Test with Mock

func TestLoadCustomer(t *testing.T) { // Create mock database mock := &MockDB{} mock.On("QueryRow", "customer-123").Return(&Customer{ ID: "customer-123", Name: "Alice", Tier: "gold", }) // Call activity with mock result, err := activities.LoadCustomer( database.WithMock(context.Background(), mock), &activities.LoadCustomerInput{CustomerID: "customer-123"}, ) assert.NoError(t, err) assert.Equal(t, "Alice", result.Name) mock.AssertExpectations(t) }

Integration Testing Workflows

Workflow Test Harness

package workflows_test import ( "context" "testing" "github.com/stretchr/testify/assert" "go.temporal.io/sdk/testsuite" "myapp/workflows" ) func TestOrderWorkflow(t *testing.T) { // Create test environment s := &testsuite.WorkflowTestSuite{} env := s.NewTestWorkflowEnvironment() // Mock activities env.OnActivity(activities.ValidateOrder, mock.MatchedBy(func(ctx context.Context, input *ValidateOrderInput) bool { return true })).Return(&ValidateOrderOutput{Valid: true}, nil) env.OnActivity(activities.ProcessPayment, mock.Anything).Return(&ProcessPaymentOutput{ PaymentID: "pay-123", Status: "success", }, nil) // Execute workflow env.ExecuteWorkflow(workflows.OrderFulfillment, OrderInput{ OrderID: "order-123", Amount: 100.00, CustomerID: "cust-123", }) // Assert assert.True(t, env.IsWorkflowCompleted()) assert.NoError(t, env.GetWorkflowError()) }

Test Data Setup

func setupTestDatabase(t *testing.T) *sql.DB { // Create test database db, err := sql.Open("postgres", "postgres://user:pass@localhost/test") assert.NoError(t, err) // Seed test data _, err = db.Exec(` INSERT INTO customers (id, name, email, tier) VALUES ('cust-1', 'Alice', 'alice@example.com', 'gold'), ('cust-2', 'Bob', 'bob@example.com', 'silver'); `) assert.NoError(t, err) t.Cleanup(func() { db.Close() }) return db }

End-to-End Testing

E2E Test Pattern

#!/bin/bash # Deploy test app cascade app apply -f test-app.yaml --namespace test # Start workflow WF_ID=$(cascade process start \ --app test-app \ --workflow TestOrderFlow \ --input '{"order_id":"test-123"}' \ --quiet) # Wait for completion cascade process inspect $WF_ID --wait # Verify result STATUS=$(cascade process inspect $WF_ID --json | jq -r '.status') [ "$STATUS" = "completed" ] || exit 1 # Verify database state ORDER=$(psql -c "SELECT * FROM orders WHERE id='test-123'") [ -n "$ORDER" ] || exit 1 echo "E2E test passed"

Debugging Techniques

1. Logs Analysis

# View workflow logs cascade logs {workflow_id} # Filter by level cascade logs {workflow_id} --level error # Search logs cascade logs {workflow_id} --grep "payment" # Follow in real-time cascade logs {workflow_id} --follow

2. Distributed Tracing

# View trace cascade trace {workflow_id} # Export for analysis cascade trace {workflow_id} --json > trace.json # Find slow operations cascade trace {workflow_id} | grep -E "activity.*[0-9]{4}ms"

3. Context Inspection

# View workflow context at current state cascade process inspect {workflow_id} --json | jq '.context' # Check specific variable cascade process inspect {workflow_id} --json | jq '.context.credit_score'

4. Activity Debugging

// Add detailed logging func LoadCustomer(ctx context.Context, input *LoadCustomerInput) (*Customer, error) { log := activity.GetLogger(ctx) log.Info("Loading customer", "customer_id", input.CustomerID) db := database.FromContext(ctx) var customer Customer err := db.QueryRowContext(ctx, "SELECT ... FROM customers WHERE id=$1", input.CustomerID). Scan(&customer.ID, &customer.Name) if err != nil { log.Error("Failed to load customer", "error", err) return nil, err } log.Info("Customer loaded successfully", "name", customer.Name) return &customer, nil }

Performance Testing

Load Test

#!/bin/bash # Test 100 concurrent workflows for i in {1..100}; do cascade process start \ --app test-app \ --workflow TestWorkflow \ --input "{\"test_id\":\"$i\"}" \ & done # Wait for all wait # Check completion rate COMPLETED=$(cascade process list --status completed --since 1m | wc -l) echo "Completed: $COMPLETED / 100"

Benchmark Activity

func BenchmarkLoadCustomer(b *testing.B) { db := setupBenchmarkDB() defer db.Close() ctx := database.WithContext(context.Background(), db) input := &LoadCustomerInput{CustomerID: "cust-123"} b.ResetTimer() for i := 0; i < b.N; i++ { _, _ = activities.LoadCustomer(ctx, input) } // Output: // BenchmarkLoadCustomer-8 10000 123456 ns/op ~0.12ms/call }

Common Issues & Solutions

Workflow Stuck

Diagnosis:

cascade process inspect {workflow_id} # Check: current_state, how_long_waiting, last_activity_result

Solutions:

  • HumanTask timeout missing: Add timeout: 24h
  • Activity never returns: Check logs, add heartbeat
  • Condition always false: Debug Choice state

Activity Timeout

Diagnosis:

cascade logs {workflow_id} | grep -i timeout

Solution:

- name: SlowActivity type: Task resource: urn:cascade:activity:slow_operation timeout: 120s # Increase timeout

Memory Leak

Diagnosis:

cascade metrics {workflow_id} # Check: memory_usage trend over time

Solution:

  • Close database connections properly
  • Clear large context variables
  • Use pagination for large datasets

Best Practices

✅ DO:

  • Write unit tests for all activities
  • Mock external services in tests
  • Test error paths
  • Use meaningful test names
  • Test boundary conditions
  • Automate E2E tests in CI/CD
  • Add logging for debugging
  • Profile performance

❌ DON’T:

  • Skip unit tests
  • Test only happy paths
  • Hard-code test data
  • Leave debugging code in production
  • Ignore slow tests
  • Create flaky tests
  • Over-test trivial code

Testing Checklist

  • Unit tests for activities
  • Integration tests for workflows
  • E2E tests for critical paths
  • Error cases covered
  • Performance benchmarks
  • Load tests
  • Security tests
  • Cleanup/teardown
  • CI/CD integration
  • Coverage >80%

Next Steps

Need workflow help?Workflow Development Guide

Activity development?Activity Development Guide

Production best practices?Best Practices: Testing


Updated: October 29, 2025
Version: 1.0
Examples: 15+ test patterns
Coverage Target: 80%+

Last updated on