Workshop 16: Testing + Production Readiness

Building Production-Grade Temporal Systems

A comprehensive testing framework and production-ready configuration for Temporal workflows, including unit tests, integration tests, mock activity implementations, worker configuration, and scalability considerations


What we want to build

A comprehensive testing framework and production-ready configuration for Temporal workflows, including:

  • Unit tests for workflows and activities using TestWorkflowRule
  • Mock activity implementations for isolated testing
  • Production worker configuration with proper scaling
  • Comprehensive error handling and monitoring patterns

Expecting Result

A complete testing and deployment solution that includes:

  • Unit tests for workflows and activities using TestWorkflowRule
  • Mock activity implementations for isolated testing
  • Production worker configuration with proper scaling
  • Comprehensive error handling and monitoring patterns
  • Performance optimization strategies
  • Deployment best practices and environment setup

Code Steps

Step 1: Define Comprehensive Data Models

Create rich data classes that support both testing and production scenarios:

data class OrderRequest(
    val orderId: String,
    val customerId: String,
    val items: List<OrderItem>,
    val shippingAddress: Address,
    val paymentMethod: PaymentMethod,
    val priority: OrderPriority = OrderPriority.NORMAL,
    val metadata: Map<String, String> = emptyMap()
)

data class OrderResult(
    val orderId: String,
    val status: OrderStatus,
    val validationResult: ValidationResult,
    val inventoryResult: InventoryResult,
    val paymentResult: PaymentResult,
    val shippingResult: ShippingResult,
    val totalProcessingTime: Long,
    val errors: List<String> = emptyList()
)

Include detailed result classes for each step that capture both success and failure scenarios


Step 2: Create Activity Interfaces with Testing in Mind

Design activity interfaces that are easy to mock and test:

@ActivityInterface
interface OrderValidationActivity {
    @ActivityMethod
    fun validateOrder(request: OrderRequest): ValidationResult

    @ActivityMethod
    fun validateCustomer(customerId: String): Boolean

    @ActivityMethod
    fun validateAddress(address: Address): Boolean
}

Create separate interfaces for each business domain to enable focused testing


Step 3: Implement Production-Ready Activity Options

Configure different activity options for different operation types:

// Standard operations
private val standardActivityOptions = ActivityOptions.newBuilder()
    .setStartToCloseTimeout(Duration.ofMinutes(5))
    .setScheduleToCloseTimeout(Duration.ofMinutes(10))
    .setRetryOptions(
        RetryOptions.newBuilder()
            .setInitialInterval(Duration.ofSeconds(1))
            .setMaximumInterval(Duration.ofSeconds(30))
            .setBackoffCoefficient(2.0)
            .setMaximumAttempts(3)
            .build()
    )
    .build()

// Critical operations (payment)
private val criticalActivityOptions = ActivityOptions.newBuilder()
    .setStartToCloseTimeout(Duration.ofMinutes(10))
    .setScheduleToCloseTimeout(Duration.ofMinutes(15))
    .setRetryOptions(
        RetryOptions.newBuilder()
            .setInitialInterval(Duration.ofSeconds(2))
            .setMaximumInterval(Duration.ofMinutes(1))
            .setBackoffCoefficient(2.0)
            .setMaximumAttempts(5)
            .build()
    )
    .build()

Step 4: Create Unit Tests with TestWorkflowRule

class OrderWorkflowTest {

    @Rule
    @JvmField
    val testWorkflowRule: TestWorkflowRule = TestWorkflowRule.newBuilder()
        .setWorkflowTypes(OrderWorkflowImpl::class.java)
        .setActivityImplementations(
            MockOrderValidationActivity(),
            MockInventoryActivity(),
            MockPaymentActivity(),
            MockShippingActivity()
        )
        .build()

    @Test
    fun testSuccessfulOrderProcessing() {
        val workflow = testWorkflowRule.workflowClient.newWorkflowStub(
            OrderWorkflow::class.java
        )

        val orderRequest = OrderRequest(
            orderId = "test-order-123",
            customerId = "customer-456",
            items = listOf(OrderItem("product-1", 2, BigDecimal("29.99"))),
            shippingAddress = Address("123 Test St", "Test City", "TC", "12345", "US"),
            paymentMethod = PaymentMethod.CREDIT_CARD
        )

        val result = workflow.processOrder(orderRequest)

        assertEquals(OrderStatus.COMPLETED, result.status)
        assertTrue(result.validationResult.isValid)
        assertTrue(result.paymentResult.success)
        assertTrue(result.errors.isEmpty())
    }
}

Step 5: Create Mock Activity Implementations

class MockOrderValidationActivity : OrderValidationActivity {

    override fun validateOrder(request: OrderRequest): ValidationResult {
        // Simulate validation logic
        return if (request.items.isNotEmpty() && request.customerId.isNotBlank()) {
            ValidationResult.success("Order validation passed")
        } else {
            ValidationResult.failure("Invalid order data")
        }
    }

    override fun validateCustomer(customerId: String): Boolean {
        // Mock customer validation
        return !customerId.startsWith("invalid")
    }

    override fun validateAddress(address: Address): Boolean {
        // Mock address validation
        return address.zipCode.length == 5
    }
}

class MockPaymentActivity : PaymentActivity {

    override fun processPayment(request: PaymentRequest): PaymentResult {
        // Simulate payment processing
        return if (request.amount > BigDecimal.ZERO) {
            PaymentResult.success("txn-${System.currentTimeMillis()}", request.amount)
        } else {
            PaymentResult.failure("Invalid payment amount")
        }
    }
}

Step 6: Production Worker Configuration

@Configuration
class TemporalWorkerConfiguration {

    @Bean
    fun temporalWorker(
        workflowClient: WorkflowClient,
        orderValidationActivity: OrderValidationActivity,
        inventoryActivity: InventoryActivity,
        paymentActivity: PaymentActivity,
        shippingActivity: ShippingActivity
    ): Worker {

        val workerFactory = WorkerFactory.newInstance(workflowClient)

        val worker = workerFactory.newWorker(
            "order-processing-queue",
            WorkerOptions.newBuilder()
                .setMaxConcurrentActivityExecutions(20)
                .setMaxConcurrentWorkflowExecutions(10)
                .setMaxConcurrentLocalActivityExecutions(10)
                .build()
        )

        // Register workflows
        worker.registerWorkflowImplementationTypes(
            OrderWorkflowImpl::class.java
        )

        // Register activities
        worker.registerActivitiesImplementations(
            orderValidationActivity,
            inventoryActivity,
            paymentActivity,
            shippingActivity
        )

        workerFactory.start()

        return worker
    }
}

Production Configuration Patterns

Worker Scaling Guidelines:

Metric Recommendation Reasoning
Max Concurrent Activities 20-50 per worker Balance throughput with resource usage
Max Concurrent Workflows 10-20 per worker Workflows are lightweight
Workers per Instance 1-3 Avoid resource contention
Task Queue Strategy Domain-specific Separate queues for different workflow types

Environment-Specific Settings:

  • Development: Lower concurrency, verbose logging
  • Staging: Production-like settings, extended timeouts
  • Production: Optimized settings, minimal logging

Step 7: Integration Testing

@SpringBootTest
@TestPropertySource(properties = ["temporal.enabled=false"])
class OrderWorkflowIntegrationTest {

    private lateinit var testWorkflowRule: TestWorkflowRule

    @BeforeEach
    fun setUp() {
        testWorkflowRule = TestWorkflowRule.newBuilder()
            .setWorkflowTypes(OrderWorkflowImpl::class.java)
            .setActivityImplementations(
                RealOrderValidationActivity(),  // Real implementations
                RealInventoryActivity(),
                MockPaymentActivity(),          // Mock external services
                MockShippingActivity()
            )
            .build()
    }

    @Test
    fun testEndToEndOrderProcessing() {
        val workflow = testWorkflowRule.workflowClient.newWorkflowStub(
            OrderWorkflow::class.java
        )

        // Test with realistic data
        val result = workflow.processOrder(createRealisticOrderRequest())

        // Verify end-to-end flow
        assertNotNull(result)
        assertEquals(OrderStatus.COMPLETED, result.status)
        assertTrue(result.totalProcessingTime > 0)
    }
}

Testing Strategies Summary

Testing Pyramid:

  • Unit Tests: Fast, isolated, mock all dependencies
  • Integration Tests: Real activities, mock external services
  • End-to-End Tests: Full system with real Temporal cluster
  • Load Tests: Performance validation under stress

Mock Strategy:

  • Mock external services (payment gateways, APIs)
  • Use real business logic in activities when possible
  • Test error scenarios with controlled failures
  • Validate retry behavior and timeout handling

💡 Key Testing & Production Patterns

What You've Learned:

  • Comprehensive testing with TestWorkflowRule
  • Mock activity implementations for isolated testing
  • Production worker configuration with proper scaling
  • Integration testing strategies
  • Performance optimization approaches
  • Deployment best practices for production systems

🚀 Final Achievement

You now master production-ready Temporal development!

Lesson 17 will cover:

  • Deployment strategies and environments
  • Monitoring and observability
  • Production operational patterns
  • Advanced scaling and optimization

Ready for production deployment mastery? Let's finish strong! 🎉

results matching ""

    No results matching ""