< Summary

Information
Class: Elsa.Testing.Shared.ActivityTestFixture
Assembly: Elsa.Testing.Shared
File(s): /home/runner/work/elsa-core/elsa-core/src/common/Elsa.Testing.Shared/ActivityTestFixture.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 71
Coverable lines: 71
Total lines: 184
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 8
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
get_Activity()100%210%
get_Services()100%210%
ConfigureServices(...)100%210%
ConfigureContext(...)100%210%
ExecuteAsync()100%210%
ExecuteAsync()100%210%
BuildAsync()0%620%
SetupExistingVariablesAsync(...)0%4260%
AddCoreWorkflowServices(...)100%210%

File(s)

/home/runner/work/elsa-core/elsa-core/src/common/Elsa.Testing.Shared/ActivityTestFixture.cs

#LineLine coverage
 1using Elsa.Common;
 2using Elsa.Expressions.Contracts;
 3using Elsa.Expressions.Services;
 4using Elsa.Extensions;
 5using Elsa.Mediator.Contracts;
 6using Elsa.Workflows;
 7using Elsa.Workflows.Activities;
 8using Elsa.Workflows.CommitStates;
 9using Elsa.Workflows.Management.Providers;
 10using Elsa.Workflows.Management.Services;
 11using Elsa.Workflows.Memory;
 12using Elsa.Workflows.PortResolvers;
 13using JetBrains.Annotations;
 14using Microsoft.Extensions.DependencyInjection;
 15using NSubstitute;
 16
 17namespace Elsa.Testing.Shared;
 18
 19/// <summary>
 20/// A test fixture for unit testing activities in isolation.
 21/// Provides a fluent API to configure services, variables, and execution context.
 22/// </summary>
 23public class ActivityTestFixture
 24{
 25    private Action<ActivityExecutionContext>? _configureContextAction;
 26
 27    /// <summary>
 28    /// Initializes a new instance of the <see cref="ActivityTestFixture"/> class.
 29    /// </summary>
 30    /// <param name="activity">The activity to test</param>
 031    public ActivityTestFixture(IActivity activity)
 32    {
 033        Activity = activity;
 034        Services = new ServiceCollection();
 035        AddCoreWorkflowServices(Services);
 036    }
 37
 38    /// <summary>
 39    /// Represents the activity being tested within the context of the activity test fixture.
 40    /// Provides access to the activity for configuration, execution, and validation purposes.
 41    /// </summary>
 042    public IActivity Activity { get; }
 43
 44    /// <summary>
 45    /// Gets the service collection for registering additional services.
 46    /// Use this to add services required by the activity under test.
 47    /// </summary>
 48    [UsedImplicitly]
 049    public IServiceCollection Services { get; private set; }
 50
 51    /// <summary>
 52    /// Configures the service collection using a fluent action.
 53    /// </summary>
 54    /// <param name="configure">Action to configure the service collection</param>
 55    /// <returns>The fixture instance for method chaining</returns>
 56    public ActivityTestFixture ConfigureServices(Action<IServiceCollection> configure)
 57    {
 058        configure(Services);
 059        return this;
 60    }
 61
 62    /// <summary>
 63    /// Configures the activity execution context before execution.
 64    /// Multiple calls to this method will chain the configuration actions together.
 65    /// </summary>
 66    /// <param name="configure">Action to configure the activity execution context</param>
 67    /// <returns>The fixture instance for method chaining</returns>
 68    [UsedImplicitly]
 69    public ActivityTestFixture ConfigureContext(Action<ActivityExecutionContext> configure)
 70    {
 071        _configureContextAction += configure;
 072        return this;
 73    }
 74
 75    /// <summary>
 76    /// Executes the activity and returns the execution context.
 77    /// </summary>
 78    /// <returns>The ActivityExecutionContext after execution</returns>
 79    public async Task<ActivityExecutionContext> ExecuteAsync()
 80    {
 081        var context = await BuildAsync();
 082        return await ExecuteAsync(context);
 083    }
 84
 85    /// <summary>
 86    /// Executes the activity using a pre-built <see cref="ActivityExecutionContext"/>.
 87    /// Useful when you need to customize the context before execution, such as setting initial workflow state or overri
 88    /// </summary>
 89    /// <param name="context">The pre-built context to execute</param>
 90    /// <returns>The <see cref="ActivityExecutionContext"/> after execution</returns>
 91    public async Task<ActivityExecutionContext> ExecuteAsync(ActivityExecutionContext context)
 92    {
 93        // Set up variables and inputs, then execute the activity
 094        await SetupExistingVariablesAsync(Activity, context);
 095        await context.EvaluateInputPropertiesAsync();
 096        context.TransitionTo(ActivityStatus.Running);
 097        await Activity.ExecuteAsync(context);
 98
 099        return context;
 0100    }
 101
 102    /// <summary>
 103    /// Builds the ActivityExecutionContext without executing the activity.
 104    /// </summary>
 105    public async Task<ActivityExecutionContext> BuildAsync()
 106    {
 0107        var serviceProvider = Services.BuildServiceProvider();
 0108        var activityRegistry = serviceProvider.GetRequiredService<IActivityRegistry>();
 0109        var workflowGraphBuilder = serviceProvider.GetRequiredService<IWorkflowGraphBuilder>();
 110
 0111        await activityRegistry.RegisterAsync(Activity.GetType());
 112
 0113        var workflow = Workflow.FromActivity(Activity);
 0114        var workflowGraph = await workflowGraphBuilder.BuildAsync(workflow);
 115
 116        // Create workflow execution context using the static factory method
 0117        var workflowExecutionContext = await WorkflowExecutionContext.CreateAsync(
 0118            serviceProvider,
 0119            workflowGraph,
 0120            $"test-instance-{Guid.NewGuid()}",
 0121            CancellationToken.None
 0122        );
 123
 124        // Create ActivityExecutionContext for the actual activity we want to test
 0125        var context = await workflowExecutionContext.CreateActivityExecutionContextAsync(Activity);
 126
 127        // Apply any context configuration action
 0128        _configureContextAction?.Invoke(context);
 129
 0130        return context;
 0131    }
 132
 133    /// <summary>
 134    /// Sets up existing variables found on the activity in the execution context.
 135    /// This is necessary because in unit tests, variables need to be initialized.
 136    /// </summary>
 137    private static Task SetupExistingVariablesAsync(IActivity activity, ActivityExecutionContext context)
 138    {
 0139        var activityType = activity.GetType();
 0140        var variableProperties = activityType.GetProperties()
 0141            .Where(p => typeof(Variable).IsAssignableFrom(p.PropertyType))
 0142            .ToList();
 143
 0144        foreach (var variable in variableProperties.Select(property => (Variable?)property.GetValue(activity)))
 145        {
 0146            if(variable == null)
 147                continue;
 148
 0149            context.WorkflowExecutionContext.MemoryRegister.Declare(variable);
 0150            variable.Set(context.ExpressionExecutionContext, variable.Value);
 151        }
 152
 0153        return Task.CompletedTask;
 154    }
 155
 156    private static void AddCoreWorkflowServices(IServiceCollection services)
 157    {
 0158        services.AddLogging();
 0159        services.AddSingleton<ISystemClock>(_ => Substitute.For<ISystemClock>());
 0160        services.AddSingleton<INotificationSender>(_ => Substitute.For<INotificationSender>());
 0161        services.AddSingleton<IActivityVisitor, ActivityVisitor>();
 0162        services.AddScoped<IExpressionEvaluator, ExpressionEvaluator>();
 0163        services.AddSingleton<IWellKnownTypeRegistry, WellKnownTypeRegistry>();
 0164        services.AddSingleton<IActivityDescriber, ActivityDescriber>();
 0165        services.AddSingleton<IPropertyDefaultValueResolver, PropertyDefaultValueResolver>();
 0166        services.AddSingleton<IPropertyUIHandlerResolver, PropertyUIHandlerResolver>();
 0167        services.AddSingleton<IActivityRegistry, ActivityRegistry>();
 0168        services.AddScoped<IActivityRegistryLookupService, ActivityRegistryLookupService>();
 0169        services.AddScoped<IIdentityGraphService, IdentityGraphService>();
 0170        services.AddScoped<IWorkflowGraphBuilder, WorkflowGraphBuilder>();
 0171        services.AddScoped<IActivityResolver, PropertyBasedActivityResolver>();
 0172        services.AddScoped<IActivityResolver, SwitchActivityResolver>();
 0173        services.AddScoped<DefaultActivityInputEvaluator>();
 0174        services.AddSingleton<IExpressionDescriptorProvider, DefaultExpressionDescriptorProvider>();
 0175        services.AddSingleton<IExpressionDescriptorRegistry, ExpressionDescriptorRegistry>();
 0176        services.AddSingleton<IIdentityGenerator>(_ => Substitute.For<IIdentityGenerator>());
 0177        services.AddSingleton<IHasher>(_ => Substitute.For<IHasher>());
 0178        services.AddSingleton<IStimulusHasher, StimulusHasher>();
 0179        services.AddSingleton<ICommitStateHandler>(_ => Substitute.For<ICommitStateHandler>());
 0180        services.AddSingleton<IActivitySchedulerFactory, ActivitySchedulerFactory>();
 0181        services.AddSingleton<IWorkflowExecutionContextSchedulerStrategy, FakeWorkflowExecutionContextSchedulerStrategy>
 0182        services.AddSingleton<IActivityExecutionContextSchedulerStrategy, FakeActivityExecutionContextSchedulerStrategy>
 0183    }
 184}