< 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: 76
Coverable lines: 76
Total lines: 192
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.Common.Multitenancy;
 3using Elsa.Common.Serialization;
 4using Elsa.Expressions.Contracts;
 5using Elsa.Expressions.Services;
 6using Elsa.Extensions;
 7using Elsa.Mediator.Contracts;
 8using Elsa.Workflows;
 9using Elsa.Workflows.Activities;
 10using Elsa.Workflows.CommitStates;
 11using Elsa.Workflows.Management.Providers;
 12using Elsa.Workflows.Management.Services;
 13using Elsa.Workflows.Memory;
 14using Elsa.Workflows.PortResolvers;
 15using Elsa.Workflows.Serialization.Serializers;
 16using JetBrains.Annotations;
 17using Microsoft.Extensions.DependencyInjection;
 18using NSubstitute;
 19
 20namespace Elsa.Testing.Shared;
 21
 22/// <summary>
 23/// A test fixture for unit testing activities in isolation.
 24/// Provides a fluent API to configure services, variables, and execution context.
 25/// </summary>
 26public class ActivityTestFixture
 27{
 28    private Action<ActivityExecutionContext>? _configureContextAction;
 29
 30    /// <summary>
 31    /// Initializes a new instance of the <see cref="ActivityTestFixture"/> class.
 32    /// </summary>
 33    /// <param name="activity">The activity to test</param>
 034    public ActivityTestFixture(IActivity activity)
 35    {
 036        Activity = activity;
 037        Services = new ServiceCollection();
 038        AddCoreWorkflowServices(Services);
 039    }
 40
 41    /// <summary>
 42    /// Represents the activity being tested within the context of the activity test fixture.
 43    /// Provides access to the activity for configuration, execution, and validation purposes.
 44    /// </summary>
 045    public IActivity Activity { get; }
 46
 47    /// <summary>
 48    /// Gets the service collection for registering additional services.
 49    /// Use this to add services required by the activity under test.
 50    /// </summary>
 51    [UsedImplicitly]
 052    public IServiceCollection Services { get; private set; }
 53
 54    /// <summary>
 55    /// Configures the service collection using a fluent action.
 56    /// </summary>
 57    /// <param name="configure">Action to configure the service collection</param>
 58    /// <returns>The fixture instance for method chaining</returns>
 59    public ActivityTestFixture ConfigureServices(Action<IServiceCollection> configure)
 60    {
 061        configure(Services);
 062        return this;
 63    }
 64
 65    /// <summary>
 66    /// Configures the activity execution context before execution.
 67    /// Multiple calls to this method will chain the configuration actions together.
 68    /// </summary>
 69    /// <param name="configure">Action to configure the activity execution context</param>
 70    /// <returns>The fixture instance for method chaining</returns>
 71    [UsedImplicitly]
 72    public ActivityTestFixture ConfigureContext(Action<ActivityExecutionContext> configure)
 73    {
 074        _configureContextAction += configure;
 075        return this;
 76    }
 77
 78    /// <summary>
 79    /// Executes the activity and returns the execution context.
 80    /// </summary>
 81    /// <returns>The ActivityExecutionContext after execution</returns>
 82    public async Task<ActivityExecutionContext> ExecuteAsync()
 83    {
 084        var context = await BuildAsync();
 085        return await ExecuteAsync(context);
 086    }
 87
 88    /// <summary>
 89    /// Executes the activity using a pre-built <see cref="ActivityExecutionContext"/>.
 90    /// Useful when you need to customize the context before execution, such as setting initial workflow state or overri
 91    /// </summary>
 92    /// <param name="context">The pre-built context to execute</param>
 93    /// <returns>The <see cref="ActivityExecutionContext"/> after execution</returns>
 94    public async Task<ActivityExecutionContext> ExecuteAsync(ActivityExecutionContext context)
 95    {
 96        // Set up variables and inputs, then execute the activity
 097        await SetupExistingVariablesAsync(Activity, context);
 098        await context.EvaluateInputPropertiesAsync();
 099        context.TransitionTo(ActivityStatus.Running);
 0100        await Activity.ExecuteAsync(context);
 101
 0102        return context;
 0103    }
 104
 105    /// <summary>
 106    /// Builds the ActivityExecutionContext without executing the activity.
 107    /// </summary>
 108    public async Task<ActivityExecutionContext> BuildAsync()
 109    {
 0110        var serviceProvider = Services.BuildServiceProvider();
 0111        var activityRegistry = serviceProvider.GetRequiredService<IActivityRegistry>();
 0112        var workflowGraphBuilder = serviceProvider.GetRequiredService<IWorkflowGraphBuilder>();
 113
 0114        await activityRegistry.RegisterAsync(Activity.GetType());
 115
 0116        var workflow = Workflow.FromActivity(Activity);
 0117        var workflowGraph = await workflowGraphBuilder.BuildAsync(workflow);
 118
 119        // Create workflow execution context using the static factory method
 0120        var workflowExecutionContext = await WorkflowExecutionContext.CreateAsync(
 0121            serviceProvider,
 0122            workflowGraph,
 0123            $"test-instance-{Guid.NewGuid()}",
 0124            CancellationToken.None
 0125        );
 126
 127        // Create ActivityExecutionContext for the actual activity we want to test
 0128        var context = await workflowExecutionContext.CreateActivityExecutionContextAsync(Activity);
 129
 130        // Apply any context configuration action
 0131        _configureContextAction?.Invoke(context);
 132
 0133        return context;
 0134    }
 135
 136    /// <summary>
 137    /// Sets up existing variables found on the activity in the execution context.
 138    /// This is necessary because in unit tests, variables need to be initialized.
 139    /// </summary>
 140    private static Task SetupExistingVariablesAsync(IActivity activity, ActivityExecutionContext context)
 141    {
 0142        var activityType = activity.GetType();
 0143        var variableProperties = activityType.GetProperties()
 0144            .Where(p => typeof(Variable).IsAssignableFrom(p.PropertyType))
 0145            .ToList();
 146
 0147        foreach (var variable in variableProperties.Select(property => (Variable?)property.GetValue(activity)))
 148        {
 0149            if(variable == null)
 150                continue;
 151
 0152            context.WorkflowExecutionContext.MemoryRegister.Declare(variable);
 0153            variable.Set(context.ExpressionExecutionContext, variable.Value);
 154        }
 155
 0156        return Task.CompletedTask;
 157    }
 158
 159    private static void AddCoreWorkflowServices(IServiceCollection services)
 160    {
 0161        services.AddLogging();
 0162        services.AddSingleton<ISystemClock>(_ => Substitute.For<ISystemClock>());
 0163        services.AddSingleton<INotificationSender>(_ => Substitute.For<INotificationSender>());
 0164        services.AddSingleton<IActivityVisitor, ActivityVisitor>();
 0165        services.AddScoped<IExpressionEvaluator, ExpressionEvaluator>();
 0166        services.AddSingleton<IWellKnownTypeRegistry, WellKnownTypeRegistry>();
 0167        services.AddSingleton<IActivityDescriber, ActivityDescriber>();
 0168        services.AddSingleton<IPropertyDefaultValueResolver, PropertyDefaultValueResolver>();
 0169        services.AddSingleton<IPropertyUIHandlerResolver, PropertyUIHandlerResolver>();
 0170        services.AddSingleton<IActivityRegistry, ActivityRegistry>();
 0171        services.AddScoped<IActivityRegistryLookupService, ActivityRegistryLookupService>();
 0172        services.AddScoped<IIdentityGraphService, IdentityGraphService>();
 0173        services.AddScoped<IWorkflowGraphBuilder, WorkflowGraphBuilder>();
 0174        services.AddScoped<IActivityResolver, PropertyBasedActivityResolver>();
 0175        services.AddScoped<IActivityResolver, SwitchActivityResolver>();
 0176        services.AddScoped<DefaultActivityInputEvaluator>();
 0177        services.AddSingleton<IExpressionDescriptorProvider, DefaultExpressionDescriptorProvider>();
 0178        services.AddSingleton<IExpressionDescriptorRegistry, ExpressionDescriptorRegistry>();
 0179        services.AddSingleton<IIdentityGenerator>(_ => Substitute.For<IIdentityGenerator>());
 0180        services.AddSingleton<IHasher>(_ => Substitute.For<IHasher>());
 0181        services.AddSingleton<IStimulusHasher, StimulusHasher>();
 0182        services.AddSingleton<ICommitStateHandler>(_ => Substitute.For<ICommitStateHandler>());
 0183        services.AddSingleton<IActivitySchedulerFactory, ActivitySchedulerFactory>();
 0184        services.AddSingleton<IWorkflowExecutionContextSchedulerStrategy, FakeWorkflowExecutionContextSchedulerStrategy>
 0185        services.AddSingleton<IActivityExecutionContextSchedulerStrategy, FakeActivityExecutionContextSchedulerStrategy>
 0186        services.AddSingleton<ITenantAccessor, DefaultTenantAccessor>();
 0187        services.AddScoped<IWorkflowStateExtractor, WorkflowStateExtractor>();
 0188        services.AddOptions();
 0189        services.AddSingleton<ISerializationTypeRegistry, SerializationTypeRegistry>();
 0190        services.AddSingleton<IPayloadSerializer, JsonPayloadSerializer>();
 0191    }
 192}