README.md
   1  # DriftKit Workflow Test Framework
   2  
   3  A comprehensive testing framework for DriftKit workflows that provides powerful mocking capabilities, execution tracking, and assertion utilities.
   4  
   5  ## Table of Contents
   6  
   7  - [Features](#features)
   8  - [Quick Start](#quick-start)
   9  - [Core Concepts](#core-concepts)
  10  - [API Reference](#api-reference)
  11  - [Examples](#examples)
  12  - [Best Practices](#best-practices)
  13  - [Troubleshooting](#troubleshooting)
  14  - [Additional Resources](#additional-resources)
  15  
  16  ## Features
  17  
  18  ✅ **Powerful Mocking System**
  19  - Mock any workflow step with custom behavior
  20  - Conditional mocking based on input data
  21  - Retry-aware mocking with failure simulation
  22  - Async step mocking support
  23  
  24  ✅ **Execution Tracking**
  25  - Track all workflow and step executions
  26  - Verify execution paths and counts
  27  - Access execution history and context
  28  
  29  ✅ **Fluent Assertions**
  30  - AssertJ-style workflow assertions
  31  - Step-specific verification
  32  - Execution order validation
  33  
  34  ✅ **Framework Integration**
  35  - Mockito integration for external dependencies
  36  - Spring Boot test support
  37  - JUnit 5 compatible
  38  
  39  ## Quick Start
  40  
  41  ### 1. Add Dependency
  42  
  43  ```xml
  44  <dependency>
  45      <groupId>ai.driftkit</groupId>
  46      <artifactId>driftkit-workflow-test-framework</artifactId>
  47      <version>0.8.8</version>
  48      <scope>test</scope>
  49  </dependency>
  50  ```
  51  
  52  ### 2. Create Your First Test
  53  
  54  ```java
  55  public class MyWorkflowTest extends WorkflowTestBase {
  56      
  57      @Test
  58      void testSimpleWorkflow() throws Exception {
  59          // Define workflow
  60          WorkflowBuilder<String, String> builder = WorkflowBuilder
  61              .define("greeting-workflow", String.class, String.class)
  62              .then("greet", (name, ctx) -> StepResult.finish("Hello, " + name));
  63          
  64          engine.register(builder);
  65          
  66          // Execute and assert
  67          String result = executeWorkflow("greeting-workflow", "World");
  68          assertEquals("Hello, World", result);
  69      }
  70  }
  71  ```
  72  
  73  ### 3. Mock Workflow Steps
  74  
  75  ```java
  76  @Test
  77  void testWithMocking() throws Exception {
  78      // Setup mock
  79      orchestrator.mock()
  80          .workflow("my-workflow")
  81          .step("external-service")
  82          .always()
  83          .thenReturn(String.class, input -> 
  84              StepResult.continueWith("Mocked response for: " + input));
  85      
  86      // Execute workflow
  87      String result = executeWorkflow("my-workflow", "test input");
  88      
  89      // Verify mock was called
  90      assertions.assertStep("my-workflow", "external-service")
  91          .wasExecuted()
  92          .withInput("test input");
  93  }
  94  ```
  95  
  96  ## Architecture Overview
  97  
  98  ### Class and Object Relationships
  99  
 100  ```
 101  ┌─────────────────────────────────────────────────────────────────────────────────────┐
 102  │                              DriftKit Workflow Test Framework                         │
 103  └─────────────────────────────────────────────────────────────────────────────────────┘
 104  
 105  ┌─────────────────────┐         ┌─────────────────────┐         ┌─────────────────────┐
 106  │  WorkflowTestBase   │────────▶│WorkflowTestOrchest. │────────▶│  WorkflowEngine     │
 107  │                     │         │                     │         │  (from workflow     │
 108  │ - engine            │         │ - engine            │         │   framework)        │
 109  │ - orchestrator      │         │ - interceptor       │         │                     │
 110  │ - assertions        │         │ - mockRegistry      │         │ - register()        │
 111  │                     │         │                     │         │ - execute()         │
 112  │ + executeWorkflow() │         │ + mock()            │         │ - addInterceptor()  │
 113  │ + executeAsync()    │         │ + getEngine()       │         │                     │
 114  └─────────────────────┘         │ + getInterceptor()  │         └─────────────────────┘
 115             │                    └─────────────────────┘                    │
 116             │                               │                               │
 117             │                               ▼                               ▼
 118             │                    ┌─────────────────────┐         ┌─────────────────────┐
 119             │                    │WorkflowTestIntercep.│◀────────│ WorkflowInterceptor │
 120             │                    │                     │         │  (interface from    │
 121             │                    │ - mockRegistry      │         │   workflow)         │
 122             │                    │ - executionTracker  │         │                     │
 123             │                    │                     │         │ + beforeStep()      │
 124             │                    │ + beforeStep()      │         │ + afterStep()       │
 125             │                    │ + afterStep()       │         │                     │
 126             │                    └─────────────────────┘         └─────────────────────┘
 127             │                               │
 128             │                               ▼
 129             │                    ┌─────────────────────┐
 130             │                    │   MockRegistry      │
 131             │                    │                     │
 132             │                    │ - mocks: Map        │
 133             │                    │                     │
 134             │                    │ + register()        │
 135             │                    │ + findMock()        │
 136             │                    │ + clear()           │
 137             │                    └─────────────────────┘
 138             │                               │
 139             │                               ▼
 140             │                    ┌─────────────────────┐         ┌─────────────────────┐
 141             │                    │    MockBuilder      │────────▶│  WorkflowMock       │
 142             │                    │                     │ creates │                     │
 143             │                    │ - workflowId       │         │ - condition         │
 144             │                    │ - stepId           │         │ - behavior          │
 145             │                    │                     │         │ - executionLimit    │
 146             │                    │ + always()          │         │                     │
 147             │                    │ + when()            │         │ + matches()         │
 148             │                    │ + times()           │         │ + execute()         │
 149             │                    │ + thenReturn()      │         │ + canExecute()      │
 150             │                    │ + thenFail()        │         │                     │
 151             │                    │ + thenSucceed()     │         └─────────────────────┘
 152             │                    └─────────────────────┘
 153 154 155  ┌─────────────────────┐         ┌─────────────────────┐         ┌─────────────────────┐
 156  │WorkflowStepAssert.  │────────▶│  ExecutionTracker   │◀────────│ StepExecution       │
 157  │                     │   uses  │                     │ tracks  │                     │
 158  │ - tracker           │         │ - executions: Map   │         │ - stepId            │
 159  │ - workflowId        │         │                     │         │ - input             │
 160  │ - stepId            │         │ + recordExecution() │         │ - output            │
 161  │                     │         │ + getExecutions()   │         │ - timestamp         │
 162  │ + wasExecuted()     │         │ + getExecutionCount()│         │ - error             │
 163  │ + wasNotExecuted()  │         │ + clear()           │         │                     │
 164  │ + withInput()       │         │                     │         └─────────────────────┘
 165  │ + producedOutput()  │         └─────────────────────┘
 166  └─────────────────────┘
 167 168 169  ┌─────────────────────┐         ┌─────────────────────────────────────────────────────┐
 170  │EnhancedWorkflowAss. │────────▶│              Workflow Framework Objects              │
 171  │                     │   uses  ├─────────────────────┬─────────────────────┬─────────┤
 172  │ + assertThat()      │         │  WorkflowBuilder    │  WorkflowExecution  │StepResult│
 173  │ + isCompleted()     │         │                     │                     │         │
 174  │ + isSuspended()     │         │ + define()          │ + getStatus()       │+ finish()│
 175  │ + isFailed()        │         │ + then()            │ + awaitResult()     │+ fail() │
 176  │ + hasResult()       │         │ + branch()          │ + resume()          │+ suspend│
 177  │ + completedWithin() │         │ + thenWithRetry()   │                     │         │
 178  └─────────────────────┘         └─────────────────────┴─────────────────────┴─────────┘
 179  
 180                                          Key Relationships:
 181                                          ─────▶ Uses/Depends on
 182                                          ◀────▶ Implements interface
 183                                          ──────  Creates/Manages
 184  ```
 185  
 186  ### Component Interactions
 187  
 188  ```
 189  ┌──────────────────────────────────────────────────────────────────────────────────────┐
 190  │                           Test Execution Flow                                         │
 191  └──────────────────────────────────────────────────────────────────────────────────────┘
 192  
 193      Your Test Class              Test Framework                    Workflow Framework
 194           │                            │                                    │
 195           │  extends                   │                                    │
 196           ├───────────────────────────▶│ WorkflowTestBase                  │
 197           │                            │                                    │
 198           │  executeWorkflow()         │                                    │
 199           ├───────────────────────────▶│                                   │
 200           │                            │                                    │
 201           │                            │  engine.execute()                 │
 202           │                            ├───────────────────────────────────▶│
 203           │                            │                                    │
 204           │                            │                                    │ WorkflowEngine
 205           │                            │◀───beforeStep()────────────────────│ (intercepts)
 206           │                            │                                    │
 207           │                            │  MockRegistry.findMock()          │
 208           │                            ├─────────────▶│                     │
 209           │                            │              │                     │
 210           │                            │◀─────────────┤ (mock found)       │
 211           │                            │              │                     │
 212           │                            │  mock.execute()                    │
 213           │                            ├─────────────▶│                     │
 214           │                            │              │                     │
 215           │                            │  ExecutionTracker.record()         │
 216           │                            ├─────────────▶│                     │
 217           │                            │              │                     │
 218           │                            │  return StepResult                 │
 219           │                            ├───────────────────────────────────▶│
 220           │                            │                                    │
 221           │                            │◀───afterStep()─────────────────────│
 222           │                            │                                    │
 223           │◀───────────────────────────┤ WorkflowExecution                 │
 224           │                            │                                    │
 225           │  assertions.assertStep()   │                                    │
 226           ├───────────────────────────▶│                                   │
 227           │                            │  ExecutionTracker.getExecutions() │
 228           │                            ├─────────────▶│                     │
 229           │                            │              │                     │
 230           │◀───────────────────────────┤ assertions pass/fail              │
 231           │                            │                                    │
 232  ```
 233  
 234  ### Key Design Principles
 235  
 236  1. **Separation of Concerns**
 237     - Test framework components are cleanly separated from workflow framework
 238     - Interceptor pattern allows non-invasive testing
 239     - Mock registry is independent of workflow execution
 240  
 241  2. **Extensibility**
 242     - Custom assertions can be added by extending base assertion classes
 243     - Mock behaviors can be composed using builder pattern
 244     - New interceptors can be added without modifying core framework
 245  
 246  3. **Type Safety**
 247     - Generic types preserve type information throughout execution
 248     - Mock builders enforce type consistency between input and output
 249     - Assertions provide compile-time type checking
 250  
 251  4. **Testability**
 252     - All components are designed to be easily testable
 253     - Clear interfaces between components
 254     - Minimal coupling between test and production code
 255  
 256  ## Core Concepts
 257  
 258  ### Test Base Class
 259  
 260  All workflow tests should extend `WorkflowTestBase`:
 261  
 262  ```java
 263  public class MyTest extends WorkflowTestBase {
 264      @Test
 265      void testMyWorkflow() throws Exception {
 266          // Your test logic here
 267      }
 268  }
 269  ```
 270  
 271  ### Mock Builder API
 272  
 273  The mock builder provides a fluent API for creating mocks:
 274  
 275  ```java
 276  // Always mock
 277  orchestrator.mock()
 278      .workflow("workflow-id")
 279      .step("step-id")
 280      .always()
 281      .thenReturn(InputType.class, input -> StepResult.continueWith(output));
 282  
 283  // Conditional mock
 284  orchestrator.mock()
 285      .workflow("workflow-id")
 286      .step("step-id")
 287      .when(Order.class, order -> order.getAmount() > 1000)
 288      .thenReturn(Order.class, order -> StepResult.continueWith(processHighValue(order)));
 289  
 290  // Failure simulation
 291  orchestrator.mock()
 292      .workflow("workflow-id")
 293      .step("step-id")
 294      .times(2).thenFail(new ServiceException("Service unavailable"))
 295      .afterwards().thenSucceed("Success after retry");
 296  ```
 297  
 298  ### Assertion API
 299  
 300  The framework provides comprehensive assertion capabilities:
 301  
 302  ```java
 303  // Basic assertions
 304  assertions.assertStep("workflow-id", "step-id")
 305      .wasExecuted()
 306      .wasExecutedTimes(3)
 307      .wasNotExecuted();
 308  
 309  // Execution order
 310  assertions.assertExecutionOrder()
 311      .step("step1")
 312      .step("step2")
 313      .step("step3");
 314  
 315  // Advanced workflow assertions
 316  EnhancedWorkflowAssertions.assertThat(execution, tracker)
 317      .isCompleted()
 318      .hasResult(expectedResult)
 319      .completedWithin(Duration.ofSeconds(5));
 320  ```
 321  
 322  ## API Reference
 323  
 324  ### WorkflowTestBase
 325  
 326  Base class for all workflow tests. Provides utilities for executing and testing workflows.
 327  
 328  **Example Usage:**
 329  
 330  ```java
 331  // Execute a workflow synchronously
 332  public class SimpleWorkflowTest extends WorkflowTestBase {
 333      @Test
 334      void testSync() throws Exception {
 335          String result = executeWorkflow("my-workflow", "input data");
 336          assertEquals("expected output", result);
 337      }
 338      
 339      // Execute asynchronously
 340      @Test
 341      void testAsync() throws Exception {
 342          CompletableFuture<String> future = executeWorkflowAsync("my-workflow", "input");
 343          String result = future.get(5, TimeUnit.SECONDS);
 344          assertNotNull(result);
 345      }
 346      
 347      // Test workflow suspension
 348      @Test
 349      void testSuspension() throws Exception {
 350          WorkflowExecution<?> execution = executeAndExpectSuspend(
 351              "approval-workflow", 
 352              new ApprovalRequest("REQ-123"),
 353              Duration.ofSeconds(2)
 354          );
 355          assertions.assertThat(execution).isSuspended();
 356      }
 357  }
 358  ```
 359  
 360  ### WorkflowTestOrchestrator
 361  
 362  Central orchestration point for test configuration and mocking.
 363  
 364  **Example Usage:**
 365  
 366  ```java
 367  // Access the orchestrator from your test
 368  @Test
 369  void testWithOrchestrator() {
 370      // Start creating a mock
 371      orchestrator.mock()
 372          .workflow("payment-workflow")
 373          .step("charge-card")
 374          .always()
 375          .thenSucceed(new PaymentResult(true, "TXN-123"));
 376      
 377      // Access the workflow engine directly
 378      WorkflowEngine engine = orchestrator.getEngine();
 379      engine.register(myWorkflowBuilder);
 380      
 381      // Access the test interceptor for advanced scenarios
 382      WorkflowTestInterceptor interceptor = orchestrator.getInterceptor();
 383      ExecutionTracker tracker = interceptor.getExecutionTracker();
 384  }
 385  ```
 386  
 387  ### MockBuilder
 388  
 389  Fluent API for creating sophisticated mocks with various behaviors.
 390  
 391  **Example Usage:**
 392  
 393  ```java
 394  // Always execute mock
 395  orchestrator.mock()
 396      .workflow("order-workflow")
 397      .step("validate")
 398      .always()
 399      .thenReturn(Order.class, order -> {
 400          if (order.isValid()) {
 401              return StepResult.continueWith(order);
 402          }
 403          return StepResult.fail("Invalid order");
 404      });
 405  
 406  // Conditional mock based on input
 407  orchestrator.mock()
 408      .workflow("pricing-workflow")
 409      .step("calculate-discount")
 410      .when(Customer.class, customer -> customer.isPremium())
 411      .thenSucceed(new Discount(0.2)); // 20% for premium
 412  
 413  // Limited execution mock (useful for retry testing)
 414  orchestrator.mock()
 415      .workflow("resilient-workflow")
 416      .step("external-api")
 417      .times(2).thenFail(new ServiceException("API down"))
 418      .afterwards().thenSucceed("API recovered");
 419  
 420  // Chain multiple behaviors
 421  orchestrator.mock()
 422      .workflow("complex-workflow")
 423      .step("process")
 424      .times(1).thenReturn(String.class, s -> StepResult.continueWith(s + "-first"))
 425      .times(1).thenReturn(String.class, s -> StepResult.continueWith(s + "-second"))
 426      .afterwards().thenFail(new RuntimeException("No more attempts"));
 427  ```
 428  
 429  ### StepAssertions
 430  
 431  Comprehensive assertions for verifying workflow execution behavior.
 432  
 433  **Example Usage:**
 434  
 435  ```java
 436  // Basic execution verification
 437  assertions.assertStep("order-workflow", "validate")
 438      .wasExecuted();
 439  
 440  // Verify non-execution
 441  assertions.assertStep("order-workflow", "rollback")
 442      .wasNotExecuted();
 443  
 444  // Verify exact execution count
 445  assertions.assertStep("payment-workflow", "charge-card")
 446      .wasExecutedTimes(3); // Retried 3 times
 447  
 448  // Verify input and output
 449  assertions.assertStep("calculation-workflow", "calculate")
 450      .wasExecuted()
 451      .withInput(new CalculationRequest(100, 0.15))
 452      .producedOutput(new CalculationResult(115));
 453  
 454  // Verify execution order
 455  assertions.assertExecutionOrder()
 456      .step("validate")
 457      .step("process") 
 458      .step("notify");
 459  
 460  // Chain multiple assertions
 461  assertions.assertStep("workflow-id", "step-id")
 462      .wasExecuted()
 463      .withInput(inputData)
 464      .producedOutput(expectedOutput)
 465      .wasExecutedTimes(1);
 466  ```
 467  
 468  ### EnhancedWorkflowAssertions
 469  
 470  Advanced assertions for workflow state and behavior using AssertJ-style API.
 471  
 472  **Example Usage:**
 473  
 474  ```java
 475  // Basic workflow assertions
 476  WorkflowExecution<?> execution = engine.execute("my-workflow", input);
 477  
 478  EnhancedWorkflowAssertions.assertThat(execution, tracker)
 479      .isCompleted()
 480      .hasResult(expectedResult)
 481      .completedWithin(Duration.ofSeconds(5))
 482      .hasNoErrors();
 483  
 484  // Suspended workflow assertions
 485  EnhancedWorkflowAssertions.assertThat(suspendedExecution, tracker)
 486      .isSuspended()
 487      .hasSuspensionData(expectedPrompt)
 488      .isWaitingForInput(UserApproval.class);
 489  
 490  // Failed workflow assertions
 491  EnhancedWorkflowAssertions.assertThat(failedExecution, tracker)
 492      .isFailed()
 493      .hasError()
 494      .hasErrorMessage("Service unavailable")
 495      .hasErrorType(ServiceException.class);
 496  
 497  // Complex assertions
 498  EnhancedWorkflowAssertions.assertThat(execution, tracker)
 499      .hasExecutedSteps("validate", "process", "notify")
 500      .hasExecutedStepsInOrder("validate", "process", "notify")
 501      .hasExecutedStepsCount(3)
 502      .completedWithin(Duration.ofMillis(500));
 503  ```
 504  
 505  ## Examples
 506  
 507  ### Testing Retry Behavior
 508  
 509  ```java
 510  @Test
 511  void testRetryMechanism() throws Exception {
 512      // Create workflow with retry
 513      WorkflowBuilder<String, String> builder = WorkflowBuilder
 514          .define("retry-workflow", String.class, String.class)
 515          .thenWithRetry("flaky-service", 
 516              (input, ctx) -> callFlakyService(input),
 517              RetryPolicyBuilder.retry()
 518                  .withMaxAttempts(3)
 519                  .withDelay(100)
 520                  .withRetryOnFailResult(true)
 521                  .build()
 522          );
 523      
 524      engine.register(builder);
 525      
 526      // Mock to fail twice then succeed
 527      orchestrator.mock()
 528          .workflow("retry-workflow")
 529          .step("flaky-service")
 530          .times(2).thenFail(new RuntimeException("Temporary failure"))
 531          .afterwards().thenSucceed("Success!");
 532      
 533      // Execute and verify
 534      String result = executeWorkflow("retry-workflow", "input");
 535      assertEquals("Success!", result);
 536  }
 537  ```
 538  
 539  ### Testing Conditional Workflows
 540  
 541  ```java
 542  @Test
 543  void testConditionalBranching() throws Exception {
 544      // Mock different responses based on input
 545      orchestrator.mock()
 546          .workflow("order-workflow")
 547          .step("process-payment")
 548          .when(Order.class, order -> order.isVip())
 549          .thenReturn(Order.class, order -> 
 550              StepResult.continueWith(new Payment(order, 0.9))); // 10% discount
 551      
 552      // Test VIP order
 553      Order vipOrder = new Order("VIP-001", 1000, true);
 554      OrderResult vipResult = executeWorkflow("order-workflow", vipOrder);
 555      assertEquals(900, vipResult.getFinalAmount()); // Discount applied
 556      
 557      // Test regular order
 558      Order regularOrder = new Order("REG-001", 1000, false);
 559      OrderResult regularResult = executeWorkflow("order-workflow", regularOrder);
 560      assertEquals(1000, regularResult.getFinalAmount()); // No discount
 561  }
 562  ```
 563  
 564  ### Testing Async Workflows
 565  
 566  ```java
 567  @Test
 568  void testAsyncWorkflow() throws Exception {
 569      // Mock async step
 570      orchestrator.mockAsync()
 571          .workflow("async-workflow")
 572          .step("long-operation")
 573          .completeAfter(Duration.ofMillis(500))
 574          .withResult("Async result");
 575      
 576      // Execute asynchronously
 577      CompletableFuture<String> future = executeWorkflowAsync("async-workflow", "input");
 578      
 579      // Verify not completed immediately
 580      assertFalse(future.isDone());
 581      
 582      // Wait and verify result
 583      String result = future.get(1, TimeUnit.SECONDS);
 584      assertEquals("Async result", result);
 585  }
 586  ```
 587  
 588  ### Testing External Service Integration
 589  
 590  ```java
 591  public class PaymentWorkflowTest extends WorkflowTestBase {
 592      
 593      @Mock
 594      private PaymentService paymentService;
 595      
 596      @BeforeEach
 597      void setUp() {
 598          MockitoAnnotations.openMocks(this);
 599          
 600          // Register workflow with mocked service
 601          WorkflowBuilder<PaymentRequest, PaymentResult> builder = WorkflowBuilder
 602              .define("payment-workflow", PaymentRequest.class, PaymentResult.class)
 603              .then("charge", (req, ctx) -> {
 604                  PaymentResponse response = paymentService.charge(req);
 605                  return StepResult.finish(new PaymentResult(response));
 606              });
 607          
 608          engine.register(builder);
 609      }
 610      
 611      @Test
 612      void testPaymentProcessing() throws Exception {
 613          // Setup Mockito mock
 614          when(paymentService.charge(any()))
 615              .thenReturn(new PaymentResponse("TXN-123", true));
 616          
 617          // Execute workflow
 618          PaymentResult result = executeWorkflow("payment-workflow", 
 619              new PaymentRequest("CARD-123", 100.00));
 620          
 621          // Verify
 622          assertTrue(result.isSuccessful());
 623          assertEquals("TXN-123", result.getTransactionId());
 624          verify(paymentService).charge(any());
 625      }
 626  }
 627  ```
 628  
 629  ### Testing Workflow Suspend and Resume
 630  
 631  ```java
 632  @Test
 633  void testWorkflowSuspendResume() throws Exception {
 634      // Create workflow that suspends for approval
 635      WorkflowBuilder<OrderRequest, OrderResult> builder = WorkflowBuilder
 636          .define("approval-workflow", OrderRequest.class, OrderResult.class)
 637          .then("validate", (order, ctx) -> StepResult.continueWith(order))
 638          .then("request-approval", (order, ctx) -> {
 639              if (order.amount > 10000) {
 640                  return StepResult.suspend(
 641                      new ApprovalPrompt("High value order requires approval"),
 642                      ApprovalResponse.class
 643                  );
 644              }
 645              return StepResult.continueWith(new ApprovalResponse(true, "AUTO"));
 646          })
 647          .then("complete", (approval, ctx) -> {
 648              OrderRequest order = (OrderRequest) ctx.getTriggerData();
 649              return StepResult.finish(new OrderResult(order.id, approval.approved));
 650          });
 651      
 652      engine.register(builder);
 653      
 654      // Test high-value order suspension
 655      OrderRequest highValueOrder = new OrderRequest("ORD-001", 15000);
 656      WorkflowExecution<?> execution = executeAndExpectSuspend(
 657          "approval-workflow", 
 658          highValueOrder,
 659          Duration.ofSeconds(2)
 660      );
 661      
 662      // Verify workflow is suspended
 663      EnhancedWorkflowAssertions.assertThat(execution, tracker)
 664          .isSuspended()
 665          .hasSuspensionData(new ApprovalPrompt("High value order requires approval"));
 666      
 667      // Resume workflow with approval
 668      execution.resume(new ApprovalResponse(true, "MANAGER-123"));
 669      OrderResult result = (OrderResult) execution.awaitResult(Duration.ofSeconds(5));
 670      
 671      // Verify final result
 672      assertTrue(result.approved);
 673      assertEquals("ORD-001", result.orderId);
 674  }
 675  ```
 676  
 677  ### Testing Complex Branching Logic
 678  
 679  ```java
 680  @Test
 681  void testComplexBranchingWorkflow() throws Exception {
 682      // Mock different paths based on customer type
 683      orchestrator.mock()
 684          .workflow("customer-workflow")
 685          .step("check-credit")
 686          .when(Customer.class, c -> c.type == CustomerType.PREMIUM)
 687          .thenSucceed(new CreditResult(50000, true));
 688      
 689      orchestrator.mock()
 690          .workflow("customer-workflow")
 691          .step("check-credit")
 692          .when(Customer.class, c -> c.type == CustomerType.REGULAR)
 693          .thenSucceed(new CreditResult(10000, true));
 694      
 695      orchestrator.mock()
 696          .workflow("customer-workflow")
 697          .step("check-credit")
 698          .when(Customer.class, c -> c.type == CustomerType.NEW)
 699          .thenSucceed(new CreditResult(0, false));
 700      
 701      // Test each customer type
 702      Customer premium = new Customer("C1", CustomerType.PREMIUM);
 703      ProcessResult premiumResult = executeWorkflow("customer-workflow", premium);
 704      assertEquals(50000, premiumResult.creditLimit);
 705      assertions.assertStep("customer-workflow", "premium-benefits").wasExecuted();
 706      assertions.assertStep("customer-workflow", "standard-processing").wasNotExecuted();
 707      
 708      Customer regular = new Customer("C2", CustomerType.REGULAR); 
 709      ProcessResult regularResult = executeWorkflow("customer-workflow", regular);
 710      assertEquals(10000, regularResult.creditLimit);
 711      assertions.assertStep("customer-workflow", "standard-processing").wasExecuted();
 712      assertions.assertStep("customer-workflow", "premium-benefits").wasNotExecuted();
 713      
 714      Customer newCustomer = new Customer("C3", CustomerType.NEW);
 715      ProcessResult newResult = executeWorkflow("customer-workflow", newCustomer);
 716      assertEquals(0, newResult.creditLimit);
 717      assertions.assertStep("customer-workflow", "onboarding").wasExecuted();
 718  }
 719  ```
 720  
 721  ## Best Practices
 722  
 723  ### Test Organization and Structure
 724  
 725  ### 1. Test Organization
 726  
 727  ```java
 728  public class OrderWorkflowTest extends WorkflowTestBase {
 729      
 730      private OrderWorkflow orderWorkflow;
 731      
 732      @BeforeEach
 733      void setUp() {
 734          // Initialize workflow once
 735          orderWorkflow = new OrderWorkflow();
 736          engine.register(orderWorkflow);
 737      }
 738      
 739      @Test
 740      void testHappyPath() { }
 741      
 742      @Test
 743      void testErrorHandling() { }
 744      
 745      @Test
 746      void testRetryScenarios() { }
 747  }
 748  ```
 749  
 750  ### 2. Mock Isolation
 751  
 752  ```java
 753  @Test
 754  void testWithIsolatedMocks() throws Exception {
 755      // Each test should set up its own mocks
 756      orchestrator.mock()
 757          .workflow("my-workflow")
 758          .step("external-api")
 759          .always()
 760          .thenReturn(Data.class, data -> processData(data));
 761      
 762      // Test logic here
 763      
 764      // Mocks are automatically cleared after each test
 765  }
 766  ```
 767  
 768  ### 3. Assertion Best Practices
 769  
 770  ```java
 771  @Test
 772  void testComplexWorkflow() throws Exception {
 773      // Execute workflow
 774      OrderResult result = executeWorkflow("order-workflow", order);
 775      
 776      // Use descriptive assertions
 777      assertThat(result)
 778          .as("Order should be processed successfully")
 779          .isNotNull()
 780          .extracting(OrderResult::getStatus)
 781          .isEqualTo("COMPLETED");
 782      
 783      // Verify execution path
 784      assertions.assertExecutionOrder()
 785          .as("Should follow happy path")
 786          .step("validate")
 787          .step("process-payment")
 788          .step("send-confirmation");
 789  }
 790  ```
 791  
 792  ### 4. Use Descriptive Test Names
 793  
 794  ```java
 795  @Test
 796  void shouldRetryPaymentThreeTimesBeforeFailing() { }
 797  
 798  @Test
 799  void shouldRoutePremiumOrdersThroughExpressProcessing() { }
 800  
 801  @Test
 802  void shouldSuspendWorkflowWhenApprovalRequired() { }
 803  ```
 804  
 805  ### 5. Retry Testing
 806  
 807  When testing retries, use the mock builder's retry-specific features:
 808  
 809  ```java
 810  @Test
 811  void testRetryBehavior() throws Exception {
 812      // Simulate failures followed by success
 813      orchestrator.mock()
 814          .workflow("payment-workflow")
 815          .step("charge-api")
 816          .times(2).thenFail(new ServiceException("Temporary outage"))
 817          .afterwards().thenSucceed(new ChargeResult("TXN-123", true));
 818      
 819      // Configure retry policy
 820      var retryPolicy = RetryPolicyBuilder.retry()
 821          .withMaxAttempts(3)
 822          .withDelay(100)
 823          .withBackoffMultiplier(2.0)
 824          .build();
 825      
 826      // Execute workflow with retry-enabled step
 827      ChargeResult result = executeWorkflow("payment-workflow", 
 828          new ChargeRequest("CARD-123", 99.99));
 829      
 830      // Verify successful completion after retries
 831      assertTrue(result.success());
 832      assertEquals("TXN-123", result.transactionId());
 833  }
 834  
 835  @Test
 836  void testRetryExhaustion() throws Exception {
 837      // Always fail to test retry exhaustion
 838      orchestrator.mock()
 839          .workflow("unreliable-workflow")
 840          .step("always-fails")
 841          .always()
 842          .thenFail(new PermanentException("Service decommissioned"));
 843      
 844      // Expect workflow to fail after max attempts
 845      assertThrows(WorkflowException.class, () -> {
 846          executeWorkflow("unreliable-workflow", "input");
 847      });
 848  }
 849  ```
 850  
 851  ## Troubleshooting
 852  
 853  ### Common Issues and Solutions
 854  
 855  **Mock Not Being Called**
 856  
 857  ```java
 858  // Problem: Mock not triggering
 859  // Solution: Check exact IDs and branch prefixes
 860  @Test
 861  void debugMockIssues() {
 862      // Enable debug logging first
 863      Logger logger = LoggerFactory.getLogger("ai.driftkit.workflow.test");
 864      ((ch.qos.logback.classic.Logger) logger).setLevel(Level.DEBUG);
 865      
 866      // Use tracker to inspect actual step IDs
 867      executeWorkflow("my-workflow", input);
 868      
 869      // Print all executed steps to see exact IDs
 870      tracker.getAllExecutions().forEach((key, executions) -> {
 871          System.out.println("Step executed: " + key);
 872      });
 873      
 874      // Common issue: branch prefixes
 875      // Instead of: orchestrator.mock().step("process")
 876      // Use: orchestrator.mock().step("true_1_process")
 877  }
 878  ```
 879  
 880  **Retry Test Issues**
 881  
 882  ```java
 883  // Problem: Retries not working with StepResult.fail()
 884  // Solution: Enable retryOnFailResult
 885  @Test 
 886  void fixRetryIssues() {
 887      var retryPolicy = RetryPolicyBuilder.retry()
 888          .withMaxAttempts(3)
 889          .withRetryOnFailResult(true)  // Critical for StepResult.fail()
 890          .build();
 891      
 892      // Mock configuration for retries
 893      orchestrator.mock()
 894          .workflow("retry-workflow")
 895          .step("flaky-step")
 896          .times(2).thenReturn(Input.class, i -> StepResult.fail("Temporary issue"))
 897          .afterwards().thenReturn(Input.class, i -> StepResult.continueWith("Success"));
 898  }
 899  ```
 900  
 901  **Async Test Timing**
 902  
 903  ```java
 904  // Problem: Async tests timing out
 905  // Solution: Adjust timeouts and verify mock delays
 906  @Test
 907  void handleAsyncTiming() {
 908      // Configure async mock with realistic delay
 909      orchestrator.mockAsync()
 910          .workflow("async-workflow") 
 911          .step("slow-operation")
 912          .completeAfter(Duration.ofMillis(200))  // Not too long
 913          .withResult("Done");
 914      
 915      // Use appropriate timeout
 916      CompletableFuture<String> future = executeWorkflowAsync("async-workflow", input);
 917      String result = future.get(1, TimeUnit.SECONDS);  // Generous timeout
 918  }
 919  ```
 920  
 921  ### Debug Utilities
 922  
 923  ```java
 924  // Utility method to debug workflow execution
 925  private void debugWorkflow(String workflowId) {
 926      System.out.println("=== Workflow Debug Info ===");
 927      
 928      // List all registered workflows
 929      engine.getRegisteredWorkflows().forEach(id -> 
 930          System.out.println("Registered workflow: " + id)
 931      );
 932      
 933      // List all mocked steps
 934      orchestrator.getInterceptor().getMockRegistry().getAllMocks()
 935          .forEach((key, mock) -> 
 936              System.out.println("Mocked step: " + key)
 937          );
 938      
 939      // Show execution history
 940      tracker.getAllExecutions().forEach((step, execs) -> {
 941          System.out.println("Step " + step + " executed " + execs.size() + " times");
 942          execs.forEach(exec -> {
 943              System.out.println("  Input: " + exec.getInput());
 944              System.out.println("  Output: " + exec.getOutput());
 945          });
 946      });
 947  }
 948  ```
 949  
 950  ## Advanced Testing Patterns
 951  
 952  ### Mock Design Patterns
 953  
 954  **1. Use Builders for Complex Mocks**
 955  
 956  ```java
 957  @Test
 958  void shouldHandleComplexPaymentScenarios() {
 959      // Create a mock builder helper
 960      MockBuilder paymentMock = orchestrator.mock()
 961          .workflow("payment-workflow")
 962          .step("charge-card");
 963      
 964      // Configure different behaviors for different inputs
 965      paymentMock.when(PaymentRequest.class, req -> req.amount > 10000)
 966          .thenReturn(PaymentRequest.class, req -> 
 967              StepResult.suspend(new FraudReviewPrompt(req), FraudDecision.class));
 968      
 969      paymentMock.when(PaymentRequest.class, req -> req.cardType == CardType.CORPORATE)
 970          .thenReturn(PaymentRequest.class, req -> 
 971              StepResult.continueWith(new PaymentResult(req.amount * 0.98))); // 2% discount
 972      
 973      paymentMock.when(PaymentRequest.class, req -> req.amount < 0)
 974          .thenFail(new ValidationException("Invalid amount"));
 975  }
 976  ```
 977  
 978  **2. Create Reusable Mock Factories**
 979  
 980  ```java
 981  public class MockFactory {
 982      
 983      public static void setupFlakeyServiceMock(WorkflowTestOrchestrator orchestrator, 
 984                                                String workflowId, 
 985                                                String stepId,
 986                                                int failureCount) {
 987          orchestrator.mock()
 988              .workflow(workflowId)
 989              .step(stepId)
 990              .times(failureCount).thenFail(new ServiceUnavailableException("Service down"))
 991              .afterwards().thenSucceed("Service recovered");
 992      }
 993  }
 994  ```
 995  
 996  ### Assertion Strategies
 997  
 998  **1. Layer Your Assertions**
 999  
1000  ```java
1001  @Test
1002  void shouldProcessOrderCompleteFlow() throws Exception {
1003      // Given
1004      Order order = new Order("ORD-123", 150.00, CustomerType.PREMIUM);
1005      
1006      // When
1007      OrderResult result = executeWorkflow("order-workflow", order);
1008      
1009      // Then - Layer 1: Basic result assertions
1010      assertNotNull(result);
1011      assertTrue(result.isSuccessful());
1012      assertEquals("ORD-123", result.orderId);
1013      
1014      // Layer 2: Execution flow assertions
1015      assertions.assertExecutionOrder()
1016          .step("validate-order")
1017          .step("check-inventory")
1018          .step("calculate-pricing")
1019          .step("process-payment")
1020          .step("ship-order")
1021          .step("send-notification");
1022      
1023      // Layer 3: Step-specific assertions
1024      assertions.assertStep("order-workflow", "calculate-pricing")
1025          .producedOutput(new PricingResult(150.00, 0.10, 135.00)); // 10% discount
1026      
1027      // Layer 4: Performance assertions
1028      EnhancedWorkflowAssertions.assertThat(execution, tracker)
1029          .completedWithin(Duration.ofSeconds(2));
1030  }
1031  ```
1032  
1033  **2. Create Custom Assertions**
1034  
1035  ```java
1036  public class OrderAssertions {
1037      
1038      public static void assertOrderProcessedCorrectly(
1039              WorkflowStepAssertions assertions, 
1040              Order order, 
1041              OrderResult result) {
1042          
1043          // Verify order basics
1044          assertEquals(order.orderId, result.orderId);
1045          
1046          // Verify workflow execution based on order type
1047          if (order.amount > 1000) {
1048              assertions.assertStep("order-workflow", "fraud-check").wasExecuted();
1049          } else {
1050              assertions.assertStep("order-workflow", "fraud-check").wasNotExecuted();
1051          }
1052          
1053          // Verify customer-specific routing
1054          if (order.customerType == CustomerType.PREMIUM) {
1055              assertions.assertStep("order-workflow", "premium-benefits").wasExecuted();
1056          }
1057      }
1058  }
1059  ```
1060  
1061  ### Performance Testing
1062  
1063  ```java
1064  @Test
1065  void shouldCompleteWithinSLA() throws Exception {
1066      // Arrange
1067      int concurrentRequests = 10;
1068      Duration maxExecutionTime = Duration.ofSeconds(5);
1069      
1070      // Act
1071      List<CompletableFuture<OrderResult>> futures = IntStream.range(0, concurrentRequests)
1072          .mapToObj(i -> executeWorkflowAsync("order-workflow", 
1073              new Order("ORD-" + i, 100.00 * i)))
1074          .collect(Collectors.toList());
1075      
1076      // Assert
1077      long startTime = System.currentTimeMillis();
1078      CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
1079          .get(maxExecutionTime.toMillis(), TimeUnit.MILLISECONDS);
1080      long executionTime = System.currentTimeMillis() - startTime;
1081      
1082      assertTrue(executionTime < maxExecutionTime.toMillis(),
1083          "Workflow execution took " + executionTime + "ms, exceeding SLA of " + maxExecutionTime);
1084      
1085      // Verify all completed successfully
1086      futures.forEach(future -> {
1087          assertTrue(future.isDone());
1088          assertDoesNotThrow(() -> future.get());
1089      });
1090  }
1091  ```
1092  
1093  ### Error Recovery Testing
1094  
1095  ```java
1096  @Test
1097  void shouldRecoverFromTransientErrors() throws Exception {
1098      // Setup recovery scenario
1099      AtomicInteger attemptCount = new AtomicInteger(0);
1100      
1101      orchestrator.mock()
1102          .workflow("resilient-workflow")
1103          .step("unstable-service")
1104          .always()
1105          .thenReturn(Request.class, req -> {
1106              int attempt = attemptCount.incrementAndGet();
1107              if (attempt <= 2) {
1108                  // Fail first two attempts
1109                  throw new TransientException("Service temporarily unavailable");
1110              }
1111              return StepResult.continueWith("Service call succeeded on attempt " + attempt);
1112          });
1113      
1114      // Execute with retry policy
1115      String result = executeWorkflow("resilient-workflow", new Request());
1116      
1117      // Verify recovery
1118      assertEquals(3, attemptCount.get());
1119      assertThat(result).contains("attempt 3");
1120  }
1121  ```
1122  
1123  ## Common Pitfalls to Avoid
1124  
1125  ### 1. Avoid Over-Mocking
1126  
1127  **❌ BAD: Mocking everything**
1128  ```java
1129  @Test
1130  void testOverMocked() {
1131      // Mocking internal logic - makes test brittle
1132      orchestrator.mock().workflow("wf").step("internal-calc").always().thenSucceed(42);
1133      orchestrator.mock().workflow("wf").step("format").always().thenSucceed("42");
1134      orchestrator.mock().workflow("wf").step("validate").always().thenSucceed(true);
1135      
1136      // Test doesn't actually test the workflow logic!
1137  }
1138  ```
1139  
1140  **✅ GOOD: Mock only external dependencies**
1141  ```java
1142  @Test
1143  void testProperMocking() {
1144      // Only mock external service calls
1145      orchestrator.mock()
1146          .workflow("calculation-workflow")
1147          .step("fetch-exchange-rate")
1148          .always()
1149          .thenSucceed(1.2); // Mock external API
1150      
1151      // Let internal calculation logic run normally
1152      CalculationResult result = executeWorkflow("calculation-workflow", 
1153          new CalculationInput(100, "USD", "EUR"));
1154      
1155      // Verify actual calculation logic
1156      assertEquals(120.0, result.convertedAmount, 0.01);
1157  }
1158  ```
1159  
1160  ### 2. Handle Timing Issues Properly
1161  
1162  **❌ BAD: Using fixed sleeps**
1163  ```java
1164  @Test
1165  void testBadTiming() throws Exception {
1166      CompletableFuture<Result> future = executeWorkflowAsync("async-workflow", input);
1167      Thread.sleep(1000); // Brittle! May fail on slow systems
1168      assertTrue(future.isDone());
1169  }
1170  ```
1171  
1172  **✅ GOOD: Use proper waiting mechanisms**
1173  ```java
1174  @Test
1175  void testProperTiming() throws Exception {
1176      CompletableFuture<Result> future = executeWorkflowAsync("async-workflow", input);
1177      
1178      // Wait with timeout
1179      Result result = future.get(5, TimeUnit.SECONDS);
1180      assertNotNull(result);
1181      
1182      // Or use assertions that wait
1183      await().atMost(Duration.ofSeconds(5))
1184          .untilAsserted(() -> {
1185              assertTrue(future.isDone());
1186              assertEquals(expectedResult, future.get());
1187          });
1188  }
1189  ```
1190  
1191  ### 3. Clean Up Resources
1192  
1193  ```java
1194  public class ResourceIntensiveTest extends WorkflowTestBase {
1195      
1196      private ExecutorService executorService;
1197      
1198      @BeforeEach
1199      void setUp() {
1200          executorService = Executors.newFixedThreadPool(10);
1201      }
1202      
1203      @AfterEach
1204      void tearDown() {
1205          // Always clean up resources
1206          if (executorService != null) {
1207              executorService.shutdownNow();
1208          }
1209      }
1210  }
1211  ```
1212  
1213  ## Additional Resources
1214  
1215  ### Advanced Examples
1216  
1217  For more complex testing scenarios, see [ADVANCED_EXAMPLES.md](ADVANCED_EXAMPLES.md) which covers:
1218  
1219  - **Stateful Workflow Testing** - Managing complex state across workflow steps
1220  - **Multi-Stage Pipeline Testing** - Testing ETL and data processing workflows  
1221  - **Event-Driven Workflow Testing** - Workflows that emit and react to events
1222  - **Saga Pattern Testing** - Distributed transactions with compensation
1223  - **Dynamic Workflow Testing** - Workflows that generate steps at runtime
1224  - **Integration Testing** - Testing with real external systems using TestContainers
1225  
1226  ### API Documentation
1227  
1228  For detailed API documentation and method signatures, refer to the Javadocs or explore the source code:
1229  
1230  - [WorkflowTestBase](src/main/java/ai/driftkit/workflow/test/core/WorkflowTestBase.java) - Base test class
1231  - [MockBuilder](src/main/java/ai/driftkit/workflow/test/mocks/MockBuilder.java) - Mock configuration API
1232  - [WorkflowStepAssertions](src/main/java/ai/driftkit/workflow/test/assertions/WorkflowStepAssertions.java) - Assertion utilities
1233  - [EnhancedWorkflowAssertions](src/main/java/ai/driftkit/workflow/test/assertions/EnhancedWorkflowAssertions.java) - Advanced assertions
1234  
1235  ### Getting Help
1236  
1237  - **Issues**: Report bugs or request features on our [GitHub Issues](https://github.com/driftkit/workflow-test-framework/issues)
1238  - **Discussions**: Join our community discussions for questions and best practices
1239  - **Documentation**: Full documentation available at [docs.driftkit.ai](https://docs.driftkit.ai)
1240  
1241  ## Contributing
1242  
1243  Contributions are welcome! Please:
1244  
1245  1. Fork the repository
1246  2. Create a feature branch (`git checkout -b feature/amazing-feature`)
1247  3. Commit your changes (`git commit -m 'Add amazing feature'`)
1248  4. Push to the branch (`git push origin feature/amazing-feature`)
1249  5. Open a Pull Request
1250  
1251  See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines.
1252  
1253  ## License
1254  
1255  This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.