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
| Type | Coverage | Speed | Cost |
|---|---|---|---|
| Unit | 80% | Fast | Low |
| Integration | 15% | Medium | Medium |
| E2E | 5% | Slow | High |
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} --follow2. 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_resultSolutions:
- 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 timeoutSolution:
- name: SlowActivity
type: Task
resource: urn:cascade:activity:slow_operation
timeout: 120s # Increase timeoutMemory Leak
Diagnosis:
cascade metrics {workflow_id}
# Check: memory_usage trend over timeSolution:
- 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