# Testing Guide Comprehensive guide to testing Embabel agents, from unit testing individual actions to end-to-end testing complete agent workflows. Embabel is designed for testability from the ground up. ## Testing Philosophy Embabel follows the same testing principles as Spring Framework: - **Unit tests** for individual components (actions, conditions) - **Integration tests** for agent workflows - **End-to-end tests** for complete business scenarios - **Test doubles** for external dependencies (LLMs, services) ### The Kitchen Testing Analogy πŸ‘¨β€πŸ³ Testing agents is like testing a professional kitchen: - **Unit tests** = Test individual cooking techniques (chop vegetables, make sauce) - **Integration tests** = Test recipe execution (making a complete dish) - **E2E tests** = Test entire service (customer order β†’ delivered meal) - **Mock services** = Practice with fake ingredients instead of expensive real ones ## Testing Strategy Overview ### Testing Pyramid for Agents ``` πŸ”Ί E2E Tests πŸ”ΊπŸ”Ί Integration Tests πŸ”ΊπŸ”ΊπŸ”Ί Unit Tests πŸ”ΊπŸ”ΊπŸ”ΊπŸ”Ί Component Tests ``` - **Component Tests** (70%) - Individual actions and conditions - **Unit Tests** (20%) - Agent behavior and business logic - **Integration Tests** (8%) - Agent workflow execution - **E2E Tests** (2%) - Complete business scenarios ## Test Infrastructure Setup ### Maven Dependencies ```xml com.embabel.agent embabel-agent-test-support ${embabel-agent.version} test org.springframework.boot spring-boot-starter-test test org.mockito mockito-kotlin test org.testcontainers junit-jupiter test ``` ### Test Configuration ```yaml # application-test.yml embabel: agent: platform: test: mock-mode: true # Use test doubles logging: personality: default # Clean test output verbosity: warn # Minimal logging infrastructure: neo4j: enabled: false # Disable external dependencies mcp: enabled: false observability: enabled: false shell: enabled: false # No interactive shell in tests spring: profiles: active: test logging: level: com.embabel: WARN org.springframework: ERROR ``` ## Unit Testing Actions ### Basic Action Testing ```kotlin @ExtendWith(MockitoExtension::class) class OrderProcessingAgentTest { @Mock private lateinit var inventoryService: InventoryService @Mock private lateinit var paymentService: PaymentService @InjectMocks private lateinit var agent: OrderProcessingAgent @Test fun `should validate order and identify risk factors`() { // Arrange val context = FakeOperationContext() context.expectResponse(OrderValidation( isValid = true, issues = listOf("High quantity order"), riskScore = 0.3 )) val order = CustomerOrder( orderId = "ORD-TEST-001", customerId = "CUSTOMER-001", items = listOf( OrderItem("LAPTOP-001", "Gaming Laptop", Money(1500.0), 10) // High quantity ), shippingAddress = Address("123 Test St", "Test City", "TC", "12345"), paymentMethod = CreditCard("1234-5678-9012-3456", LocalDate.now().plusYears(2)) ) // Act val result = agent.validateOrder(order, context) // Assert assertThat(result.isValid).isTrue() assertThat(result.issues).contains("High quantity order") assertThat(result.riskScore).isEqualTo(0.3) // Verify LLM interaction val llmInvocation = context.llmInvocations.first() assertThat(llmInvocation.prompt).contains("ORD-TEST-001") assertThat(llmInvocation.prompt).contains("Gaming Laptop") assertThat(llmInvocation.prompt).contains("quantity: 10") } @Test fun `should check inventory for all order items`() { // Arrange `when`(inventoryService.getAvailableQuantity("LAPTOP-001")).thenReturn(15) `when`(inventoryService.getAvailableQuantity("MOUSE-002")).thenReturn(0) `when`(inventoryService.getExpectedRestockDate("MOUSE-002")) .thenReturn(LocalDate.now().plusWeeks(2)) val order = CustomerOrder( orderId = "ORD-TEST-002", customerId = "CUSTOMER-002", items = listOf( OrderItem("LAPTOP-001", "Gaming Laptop", Money(1500.0), 2), OrderItem("MOUSE-002", "Gaming Mouse", Money(75.0), 3) ), shippingAddress = Address("456 Test Ave", "Test Town", "TT", "54321"), paymentMethod = CreditCard("9876-5432-1098-7654", LocalDate.now().plusYears(1)) ) // Act val result = agent.checkInventory(order) // Assert assertThat(result).hasSize(2) val laptopCheck = result.find { it.productId == "LAPTOP-001" }!! assertThat(laptopCheck.availableQuantity).isEqualTo(15) assertThat(laptopCheck.expectedRestockDate).isNull() val mouseCheck = result.find { it.productId == "MOUSE-002" }!! assertThat(mouseCheck.availableQuantity).isEqualTo(0) assertThat(mouseCheck.expectedRestockDate).isNotNull() // Verify service interactions verify(inventoryService).getAvailableQuantity("LAPTOP-001") verify(inventoryService).getAvailableQuantity("MOUSE-002") verify(inventoryService).getExpectedRestockDate("MOUSE-002") } } ``` ### Testing LLM Interactions ```kotlin class LlmInteractionTest { @Test fun `should use correct model and temperature for creative tasks`() { val context = FakeOperationContext() context.expectResponse(ProductRecommendations( recommendations = listOf( Recommendation("Wireless Headphones", "Perfect for gaming", 4.5), Recommendation("RGB Keyboard", "Enhance your setup", 4.3) ) )) val agent = RecommendationAgent() val customer = Customer("John", interests = listOf("gaming", "technology")) agent.generateRecommendations(customer, context) val llmInvocation = context.llmInvocations.first() // Verify model selection assertThat(llmInvocation.interaction.llmOptions.model) .isEqualTo(OpenAiModels.GPT_4O_MINI) // Verify temperature for creativity assertThat(llmInvocation.interaction.llmOptions.temperature) .isEqualTo(0.8) // Verify prompt engineering assertThat(llmInvocation.prompt).contains("gaming") assertThat(llmInvocation.prompt).contains("technology") assertThat(llmInvocation.prompt).contains("recommendations") } @Test fun `should handle LLM errors gracefully`() { val context = FakeOperationContext() context.expectLlmError(RuntimeException("LLM service unavailable")) val agent = RecommendationAgent() val customer = Customer("Jane", interests = listOf("books")) // Should not throw exception assertThatCode { agent.generateRecommendations(customer, context) }.doesNotThrowAnyException() // Should log error appropriately // ... verify logging behavior } } ``` ### Testing Tool Integration ```kotlin class WebSearchActionTest { @Test fun `should use web tools for market research`() { val context = FakeOperationContext() context.expectResponse(MarketResearch( trends = listOf("Increased demand for sustainable products"), competitors = listOf("EcoTech Corp", "GreenSolutions Inc"), insights = "Market growing at 15% annually" )) val agent = MarketResearchAgent() val query = ResearchQuery("sustainable technology trends 2024") val result = agent.conductMarketResearch(query, context) // Verify tool groups were specified val toolGroups = context.llmInvocations.first().interaction.toolGroups assertThat(toolGroups).contains(ToolGroup.WEB) // Verify result structure assertThat(result.trends).isNotEmpty() assertThat(result.competitors).hasSize(2) assertThat(result.insights).contains("15%") } } ``` ## Integration Testing Agent Workflows ### Agent Integration Test Setup ```kotlin @SpringBootTest( classes = [TestConfiguration::class], webEnvironment = SpringBootTest.WebEnvironment.NONE ) @TestPropertySource(properties = [ "embabel.agent.platform.test.mockMode=true", "embabel.agent.logging.personality=default" ]) class OrderProcessingAgentIntegrationTest { @Autowired private lateinit var agentPlatform: AgentPlatform @Autowired private lateinit var orderProcessingAgent: OrderProcessingAgent @MockBean private lateinit var inventoryService: InventoryService @MockBean private lateinit var paymentService: PaymentService @TestConfiguration class TestConfiguration { @Bean @Primary fun testOrderProcessingAgent( inventoryService: InventoryService, paymentService: PaymentService, fulfillmentService: FulfillmentService, customerService: CustomerService ): OrderProcessingAgent { return OrderProcessingAgent( inventoryService, paymentService, fulfillmentService, customerService ) } } } ``` ### Testing Complete Workflows ```kotlin class CompleteWorkflowTest : OrderProcessingAgentIntegrationTest() { @Test fun `should process standard order from validation to fulfillment`() { // Arrange - Mock external services `when`(inventoryService.getAvailableQuantity(any())).thenReturn(100) `when`(paymentService.processPayment(any(), any())).thenReturn( PaymentResult(success = true, transactionId = "TXN-12345", errorMessage = null) ) val order = CustomerOrder( orderId = "ORD-INTEGRATION-001", customerId = "CUSTOMER-STANDARD", items = listOf( OrderItem("BOOK-001", "Spring Framework Guide", Money(45.0), 1), OrderItem("PEN-002", "Premium Pen", Money(15.0), 2) ), shippingAddress = Address("789 Integration St", "Test City", "TC", "11111"), paymentMethod = CreditCard("1111-2222-3333-4444", LocalDate.now().plusYears(1)) ) // Act - Execute agent in focused mode val result = agentPlatform.run(orderProcessingAgent::class, order) // Assert - Verify complete workflow execution assertThat(result).isInstanceOf() assertThat(result.status).isEqualTo(OrderStatus.COMPLETED) assertThat(result.originalOrder.orderId).isEqualTo("ORD-INTEGRATION-001") assertThat(result.trackingNumber).isNotNull() assertThat(result.estimatedDelivery).isAfter(LocalDate.now()) // Verify service interactions occurred verify(inventoryService, atLeastOnce()).getAvailableQuantity(any()) verify(paymentService).processPayment(any(), any()) } @Test fun `should handle VIP customer orders with priority processing`() { // Arrange - VIP customer scenario `when`(inventoryService.getAvailableQuantity(any())).thenReturn(50) `when`(paymentService.processPayment(any(), any())).thenReturn( PaymentResult(success = true, transactionId = "TXN-VIP-789", errorMessage = null) ) val vipOrder = CustomerOrder( orderId = "ORD-VIP-001", customerId = "VIP-CUSTOMER-001", // VIP customer items = listOf( OrderItem("LUXURY-LAPTOP", "Premium Gaming Laptop", Money(3500.0), 1) ), shippingAddress = Address("1 Executive Plaza", "Premium City", "PC", "99999"), paymentMethod = CreditCard("9999-8888-7777-6666", LocalDate.now().plusYears(3)) ) // Act val result = agentPlatform.run(orderProcessingAgent::class, vipOrder) // Assert - VIP processing characteristics assertThat(result.status).isEqualTo(OrderStatus.COMPLETED) assertThat(result.estimatedDelivery).isBefore( LocalDate.now().plusDays(2) // Expedited shipping ) assertThat(result.internalNotes).containsIgnoringCase("VIP") } @Test fun `should handle inventory shortage scenarios`() { // Arrange - Out of stock scenario `when`(inventoryService.getAvailableQuantity("RARE-ITEM-001")).thenReturn(0) `when`(inventoryService.getExpectedRestockDate("RARE-ITEM-001")) .thenReturn(LocalDate.now().plusWeeks(3)) val backorderOrder = CustomerOrder( orderId = "ORD-BACKORDER-001", customerId = "CUSTOMER-PATIENT", items = listOf( OrderItem("RARE-ITEM-001", "Limited Edition Collectible", Money(500.0), 1) ), shippingAddress = Address("456 Collector Lane", "Hobby City", "HC", "22222"), paymentMethod = CreditCard("2222-3333-4444-5555", LocalDate.now().plusYears(2)) ) // Act - Agent should adapt to inventory constraints val result = agentPlatform.run(orderProcessingAgent::class, backorderOrder) // Assert - Graceful handling of out-of-stock assertThat(result.customerMessage).contains("out of stock", "backorder") assertThat(result.estimatedDelivery).isAfter(LocalDate.now().plusWeeks(2)) } } ``` ### Testing Agent Modes ```kotlin class AgentModeTest { @Test fun `focused mode should execute specific agent`() { val userInput = "Process order ORD-123 for customer CUST-456" // Execute in focused mode - targets specific agent val result = agentPlatform.run(OrderProcessingAgent::class, userInput) assertThat(result).isInstanceOf() } @Test fun `closed mode should select appropriate agent dynamically`() { val userInput = "I want to place an order for some books" // Execute in closed mode - platform selects best agent val result = agentPlatform.execute(userInput) // Platform should have selected OrderProcessingAgent assertThat(result).isInstanceOf() } @Test fun `open mode should combine multiple agent capabilities`() { val complexRequest = """ Research the latest sustainable technology trends, recommend products that match those trends, and create a purchase plan within a $2000 budget """.trimIndent() // Execute in open mode - combines research + recommendation + ordering val result = agentPlatform.executeOpen(complexRequest) // Should combine capabilities from multiple agents assertThat(result).isInstanceOf() } } ``` ## Testing Conditions and Business Rules ### Custom Condition Testing ```kotlin class OrderProcessingConditionsTest { private lateinit var conditions: OrderProcessingConditions @BeforeEach fun setUp() { conditions = OrderProcessingConditions() } @Test fun `should require VIP processing for high-value orders`() { val highValueOrder = CustomerOrder( orderId = "ORD-HIGH-001", customerId = "CUSTOMER-001", items = listOf( OrderItem("EXPENSIVE-ITEM", "Luxury Product", Money(5000.0), 1) ), shippingAddress = Address("123 Rich St", "Wealthy City", "WC", "99999"), paymentMethod = CreditCard("1234-5678-9012-3456", LocalDate.now().plusYears(2)) ) val requiresPriority = conditions.requiresPriorityProcessing(highValueOrder) assertThat(requiresPriority).isTrue() } @Test fun `should validate inventory availability`() { val order = CustomerOrder( orderId = "ORD-INV-001", customerId = "CUSTOMER-002", items = listOf( OrderItem("ITEM-A", "Product A", Money(100.0), 5), OrderItem("ITEM-B", "Product B", Money(50.0), 3) ), shippingAddress = Address("456 Test Ave", "Test City", "TC", "12345"), paymentMethod = CreditCard("9876-5432-1098-7654", LocalDate.now().plusYears(1)) ) val inventoryChecks = listOf( InventoryCheck("ITEM-A", availableQuantity = 10, expectedRestockDate = null), InventoryCheck("ITEM-B", availableQuantity = 2, expectedRestockDate = LocalDate.now().plusWeeks(1)) ) val hasInventory = conditions.hasInventoryAvailable(order, inventoryChecks) assertThat(hasInventory).isFalse() // Item B has insufficient quantity } } ``` ### Testing Condition Evaluation ```kotlin class ConditionEvaluationTest { @Test fun `should evaluate multiple conditions correctly`() { val testContext = TestAgentContext() testContext.addDomainObject(order) testContext.addDomainObject(inventoryChecks) testContext.addDomainObject(customer) val conditionResults = testContext.evaluateConditions( "hasInventoryAvailable", "requiresPriorityProcessing", "hasValidPaymentMethod" ) assertThat(conditionResults).containsExactly( "hasInventoryAvailable" to true, "requiresPriorityProcessing" to false, "hasValidPaymentMethod" to true ) } } ``` ## End-to-End Testing ### Complete Business Scenario Testing ```kotlin @SpringBootTest @Testcontainers class OrderProcessingE2ETest { @Container static val postgres = PostgreSQLContainer("postgres:13").apply { withDatabaseName("embabel_test") withUsername("test") withPassword("test") } @Container static val redis = GenericContainer("redis:6-alpine").apply { withExposedPorts(6379) } @DynamicPropertySource companion object { @JvmStatic fun configureProperties(registry: DynamicPropertyRegistry) { registry.add("spring.datasource.url", postgres::getJdbcUrl) registry.add("spring.datasource.username", postgres::getUsername) registry.add("spring.datasource.password", postgres::getPassword) registry.add("spring.redis.host", redis::getHost) registry.add("spring.redis.port", redis::getFirstMappedPort) } } @Autowired private lateinit var agentPlatform: AgentPlatform @Autowired private lateinit var orderRepository: OrderRepository @Test fun `should process order end-to-end with real infrastructure`() { // Arrange - Create realistic test data val customerOrder = CustomerOrder( orderId = "ORD-E2E-${UUID.randomUUID()}", customerId = "CUST-E2E-001", items = listOf( OrderItem("BOOK-SPRING", "Spring Framework 6 Guide", Money(65.99), 1), OrderItem("STICKER-KOTLIN", "Kotlin Logo Stickers", Money(9.99), 2) ), shippingAddress = Address( street = "123 Developer Way", city = "Code City", state = "CA", zipCode = "94105" ), paymentMethod = CreditCard( number = "4111-1111-1111-1111", // Test card number expiryDate = LocalDate.now().plusYears(2) ) ) // Act - Execute complete workflow val startTime = Instant.now() val result = agentPlatform.run(OrderProcessingAgent::class, customerOrder) val endTime = Instant.now() // Assert - Verify complete end-to-end processing assertThat(result).isInstanceOf() assertThat(result.status).isEqualTo(OrderStatus.COMPLETED) assertThat(result.trackingNumber).isNotNull() // Verify data persistence val savedOrder = orderRepository.findById(customerOrder.orderId) assertThat(savedOrder).isPresent assertThat(savedOrder.get().status).isEqualTo(OrderStatus.COMPLETED) // Verify performance val processingTime = Duration.between(startTime, endTime) assertThat(processingTime).isLessThan(Duration.ofSeconds(30)) // Verify business rules assertThat(result.estimatedDelivery).isAfter(LocalDate.now()) assertThat(result.customerMessage).contains("order has been processed") } @Test fun `should handle high-volume concurrent order processing`() { val orderCount = 10 val orders = (1..orderCount).map { index -> CustomerOrder( orderId = "ORD-CONCURRENT-$index", customerId = "CUST-LOAD-$index", items = listOf( OrderItem("ITEM-$index", "Test Product $index", Money(25.0), 1) ), shippingAddress = Address("$index Test St", "Test City", "TC", "12345"), paymentMethod = CreditCard("4111-1111-1111-111$index", LocalDate.now().plusYears(1)) ) } // Execute orders concurrently val results = orders.parallelStream() .map { order -> agentPlatform.run(OrderProcessingAgent::class, order) } .collect(Collectors.toList()) // Verify all orders processed successfully assertThat(results).hasSize(orderCount) assertThat(results).allMatch { it.status == OrderStatus.COMPLETED } // Verify all orders persisted val savedOrders = orderRepository.findAllById(orders.map { it.orderId }) assertThat(savedOrders).hasSize(orderCount) } } ``` ## Performance Testing ### Agent Performance Benchmarks ```kotlin @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.MILLISECONDS) @State(Scope.Benchmark) class AgentPerformanceBenchmark { private lateinit var agentPlatform: AgentPlatform private lateinit var testOrder: CustomerOrder @Setup fun setup() { agentPlatform = createTestAgentPlatform() testOrder = createStandardTestOrder() } @Benchmark fun benchmarkOrderProcessing(): ProcessedOrder { return agentPlatform.run(OrderProcessingAgent::class, testOrder) } @Benchmark fun benchmarkAgentSelection(): ProcessedOrder { return agentPlatform.execute("Process order for customer CUST-BENCH-001") } @Benchmark fun benchmarkComplexPlanning(): ComprehensivePlan { return agentPlatform.executeOpen(""" Research market trends for sustainable technology, recommend products under $1000 budget, create purchase timeline and order plan """.trimIndent()) } } ``` ### Load Testing Patterns ```kotlin @Test fun `should maintain performance under load`() { val threadCount = 20 val executionsPerThread = 50 val executor = Executors.newFixedThreadPool(threadCount) val latch = CountDownLatch(threadCount) val results = Collections.synchronizedList(mutableListOf()) val errors = Collections.synchronizedList(mutableListOf()) repeat(threadCount) { executor.submit { try { repeat(executionsPerThread) { iteration -> val order = createTestOrder("LOAD-${Thread.currentThread().id}-$iteration") val result = agentPlatform.run(OrderProcessingAgent::class, order) results.add(result) } } catch (e: Exception) { errors.add(e) } finally { latch.countDown() } } } // Wait for all threads to complete assertThat(latch.await(60, TimeUnit.SECONDS)).isTrue() // Verify results assertThat(errors).isEmpty() assertThat(results).hasSize(threadCount * executionsPerThread) assertThat(results).allMatch { it.status == OrderStatus.COMPLETED } } ``` ## Test Data Management ### Test Data Builders ```kotlin class OrderTestDataBuilder { companion object { fun standardOrder(): CustomerOrder { return CustomerOrder( orderId = "ORD-STD-${UUID.randomUUID()}", customerId = "CUST-STANDARD", items = listOf( OrderItem("LAPTOP-001", "Business Laptop", Money(899.99), 1) ), shippingAddress = standardAddress(), paymentMethod = validCreditCard() ) } fun vipOrder(): CustomerOrder { return CustomerOrder( orderId = "ORD-VIP-${UUID.randomUUID()}", customerId = "VIP-CUSTOMER-001", items = listOf( OrderItem("LUXURY-001", "Premium Product", Money(2500.0), 1) ), shippingAddress = vipAddress(), paymentMethod = premiumCreditCard() ) } fun backorderOrder(): CustomerOrder { return CustomerOrder( orderId = "ORD-BACK-${UUID.randomUUID()}", customerId = "CUST-PATIENT", items = listOf( OrderItem("RARE-001", "Limited Edition Item", Money(150.0), 1) ), shippingAddress = standardAddress(), paymentMethod = validCreditCard() ) } private fun standardAddress() = Address( street = "123 Test Street", city = "Test City", state = "TC", zipCode = "12345" ) private fun vipAddress() = Address( street = "1 Executive Plaza", city = "Premium City", state = "PC", zipCode = "99999" ) private fun validCreditCard() = CreditCard( number = "4111-1111-1111-1111", expiryDate = LocalDate.now().plusYears(2) ) private fun premiumCreditCard() = CreditCard( number = "5555-5555-5555-4444", expiryDate = LocalDate.now().plusYears(3) ) } } ``` ### Test Fixtures and Scenarios ```kotlin @TestConfiguration class TestFixtureConfiguration { @Bean @Primary fun testInventoryService(): InventoryService { return object : InventoryService { override fun getAvailableQuantity(productId: String): Int { return when (productId) { "LAPTOP-001" -> 25 "LUXURY-001" -> 5 "RARE-001" -> 0 // Out of stock else -> 10 } } override fun getExpectedRestockDate(productId: String): LocalDate? { return if (getAvailableQuantity(productId) == 0) { LocalDate.now().plusWeeks(2) } else null } } } @Bean @Primary fun testPaymentService(): PaymentService { return object : PaymentService { override fun processPayment(paymentMethod: PaymentMethod, amount: Money): PaymentResult { return when { amount.amount > 10000.0 -> PaymentResult( success = false, transactionId = null, errorMessage = "Amount exceeds limit" ) paymentMethod.isExpired() -> PaymentResult( success = false, transactionId = null, errorMessage = "Payment method expired" ) else -> PaymentResult( success = true, transactionId = "TXN-TEST-${UUID.randomUUID()}", errorMessage = null ) } } } } } ``` ## Testing Best Practices ### 1. Test Organization ```kotlin // Use nested test classes for different scenarios @DisplayName("Order Processing Agent") class OrderProcessingAgentTest { @Nested @DisplayName("Order Validation") inner class OrderValidationTests { // Validation-specific tests } @Nested @DisplayName("Inventory Management") inner class InventoryTests { // Inventory-specific tests } @Nested @DisplayName("Payment Processing") inner class PaymentTests { // Payment-specific tests } } ``` ### 2. Assertion Patterns ```kotlin // Use domain-specific assertions fun assertThat(processedOrder: ProcessedOrder): ProcessedOrderAssert { return ProcessedOrderAssert(processedOrder) } class ProcessedOrderAssert(private val actual: ProcessedOrder) : AbstractAssert(actual, ProcessedOrderAssert::class.java) { fun isCompleted(): ProcessedOrderAssert { if (actual.status != OrderStatus.COMPLETED) { failWithMessage("Expected order to be completed but was <%s>", actual.status) } return this } fun hasTrackingNumber(): ProcessedOrderAssert { if (actual.trackingNumber.isNullOrBlank()) { failWithMessage("Expected order to have tracking number but was null or blank") } return this } fun hasEstimatedDeliveryAfter(date: LocalDate): ProcessedOrderAssert { if (actual.estimatedDelivery == null || !actual.estimatedDelivery!!.isAfter(date)) { failWithMessage("Expected delivery date after <%s> but was <%s>", date, actual.estimatedDelivery) } return this } } // Usage: assertThat(result) .isCompleted() .hasTrackingNumber() .hasEstimatedDeliveryAfter(LocalDate.now()) ``` ### 3. Test Documentation ```kotlin @Test @DisplayName("Should process VIP orders with expedited shipping when customer has VIP status") fun `should process VIP orders with expedited shipping`() { // Given: A VIP customer with a high-value order val vipOrder = OrderTestDataBuilder.vipOrder() // When: The order is processed through the agent val result = agentPlatform.run(OrderProcessingAgent::class, vipOrder) // Then: The order should be completed with VIP treatment assertThat(result) .isCompleted() .hasEstimatedDeliveryAfter(LocalDate.now()) .hasCustomerMessage(containing("VIP")) } ``` ### 4. Test Isolation ```kotlin @TestMethodOrder(OrderAnnotation::class) class IsolatedAgentTest { @Test @Order(1) fun `test should not affect other tests`() { // Each test should be completely independent // Use @DirtiesContext if needed for Spring context } @BeforeEach fun resetState() { // Reset any shared state TestDataManager.clearAll() FakeOperationContext.resetGlobalState() } } ``` ## Continuous Integration Testing ### CI Pipeline Configuration ```yaml # .github/workflows/test.yml name: Agent Testing Pipeline on: [push, pull_request] jobs: unit-tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-java@v3 with: java-version: '21' - run: mvn test -Dtest=**/*Test integration-tests: runs-on: ubuntu-latest services: postgres: image: postgres:13 env: POSTGRES_PASSWORD: test options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - uses: actions/checkout@v3 - uses: actions/setup-java@v3 with: java-version: '21' - run: mvn test -Dtest=**/*IntegrationTest env: SPRING_DATASOURCE_URL: jdbc:postgresql://localhost/test SPRING_DATASOURCE_USERNAME: postgres SPRING_DATASOURCE_PASSWORD: test e2e-tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-java@v3 with: java-version: '21' - run: mvn test -Dtest=**/*E2ETest env: OPENAI_API_KEY: ${{ secrets.TEST_OPENAI_API_KEY }} ``` This comprehensive testing guide ensures your Embabel agents are robust, reliable, and maintainable across all scenarios from simple unit tests to complex end-to-end business workflows.