using Elsa.Extensions; using Elsa.Features.Services; using Elsa.Workflows; using Elsa.Workflows.Activities; using Elsa.Workflows.Models; using Elsa.Workflows.State; using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; using Xunit.Abstractions; namespace Elsa.Testing.Shared; /// /// A test fixture for integration testing workflows and activities. /// Provides a fluent API to configure services, run workflows, and capture output. /// public class WorkflowTestFixture { private readonly TestApplicationBuilder _testApplicationBuilder; private IServiceProvider? _services; /// /// Initializes a new instance of the class. /// /// The test output helper public WorkflowTestFixture(ITestOutputHelper testOutputHelper) { _testApplicationBuilder = new TestApplicationBuilder(testOutputHelper); CapturingTextWriter = new CapturingTextWriter(); _testApplicationBuilder.WithCapturingTextWriter(CapturingTextWriter); } /// /// Gets the capturing text writer that captures standard output from WriteLine activities. /// public CapturingTextWriter CapturingTextWriter { get; } /// /// Gets the service provider. Throws if Build() hasn't been called yet. /// private IServiceProvider Services => _services ?? throw new InvalidOperationException("Build() must be called before accessing services"); /// /// Configures Elsa features. /// /// Action to configure Elsa /// The fixture instance for method chaining [UsedImplicitly] public WorkflowTestFixture ConfigureElsa(Action configure) { _testApplicationBuilder.ConfigureElsa(configure); return this; } /// /// Configures the service collection. /// /// Action to configure the service collection /// The fixture instance for method chaining [UsedImplicitly] public WorkflowTestFixture ConfigureServices(Action configure) { _testApplicationBuilder.ConfigureServices(configure); return this; } /// /// Adds a workflow to the service provider. /// [UsedImplicitly] public WorkflowTestFixture AddWorkflow() where T : IWorkflow { _testApplicationBuilder.AddWorkflow(); return this; } /// /// Adds activities from the assembly containing the specified type. /// [UsedImplicitly] public WorkflowTestFixture AddActivitiesFrom() { _testApplicationBuilder.AddActivitiesFrom(); return this; } /// /// Adds workflows from the specified relative directory. /// /// The path segments of the directory [UsedImplicitly] public WorkflowTestFixture WithWorkflowsFromDirectory(params string[] directory) { _testApplicationBuilder.WithWorkflowsFromDirectory(directory); return this; } /// /// Builds the service provider and populates registries. /// Must be called before running workflows. /// public async Task BuildAsync() { _services = _testApplicationBuilder.Build(); await Services.PopulateRegistriesAsync(); return this; } /// /// Runs a workflow and returns the workflow result. /// Automatically builds the fixture if not already built. /// /// The workflow to run /// Cancellation token /// The workflow result after execution public async Task RunWorkflowAsync(IWorkflow workflow, CancellationToken cancellationToken = default) { if (_services == null) await BuildAsync(); var workflowRunner = Services.GetRequiredService(); return await workflowRunner.RunAsync(workflow, cancellationToken: cancellationToken); } /// /// Runs an activity wrapped in a workflow and returns the workflow result. /// Automatically builds the fixture if not already built. /// /// The activity to run /// Cancellation token /// The workflow result after execution public async Task RunActivityAsync(IActivity activity, CancellationToken cancellationToken = default) { if (_services == null) await BuildAsync(); var workflowRunner = Services.GetRequiredService(); return await workflowRunner.RunAsync(activity, cancellationToken: cancellationToken); } /// /// Runs a workflow by definition ID and returns the workflow state. /// Automatically builds the fixture if not already built. /// /// The workflow definition ID /// Optional input dictionary /// The workflow state after execution public async Task RunWorkflowAsync(string definitionId, IDictionary? input = null) { if (_services == null) await BuildAsync(); return await Services.RunWorkflowUntilEndAsync(definitionId, input); } }