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);
}
}