elsa-core/test/integration/Elsa.Activities.IntegrationTests/ParallelForEachTests.cs
Sipke Schoorstra 7a66926f16
Add unit and integration tests for ParallelForEach activity covering behavior with different item types, fault handling, and scenario-specific conditions (#7120)
* Add unit and integration tests for `ParallelForEach` activity covering behavior with different item types, fault handling, and scenario-specific conditions

* Replace `Array.Empty<string>()` with `[]` in `ParallelForEachTests` to simplify syntax.
2025-11-27 20:58:58 +01:00

135 lines
4.9 KiB
C#

using Elsa.Extensions;
using Elsa.Testing.Shared;
using Elsa.Workflows;
using Elsa.Workflows.Activities;
using Elsa.Workflows.Models;
using Xunit.Abstractions;
namespace Elsa.Activities.IntegrationTests;
public class ParallelForEachTests(ITestOutputHelper testOutputHelper)
{
private readonly WorkflowTestFixture _fixture = new(testOutputHelper);
private const string CurrentValueVar = "CurrentValue";
private static readonly string[] ThreeItems = ["a", "b", "c"];
private CapturingTextWriter CapturingTextWriter => _fixture.CapturingTextWriter;
[Theory(DisplayName = "ParallelForEach executes body for all items")]
[MemberData(nameof(ItemTestCases))]
public async Task ParallelForEach_ExecutesBody_ForAllItems(string[] items)
{
await ExecuteAndAssertAllItems(items);
}
[Fact(DisplayName = "ParallelForEach completes when collection is empty")]
public async Task ParallelForEach_Completes_WhenCollectionEmpty()
{
await ExecuteAndAssertStatus([], ActivityStatus.Completed);
Assert.Empty(CapturingTextWriter.Lines);
}
[Fact(DisplayName = "ParallelForEach completes when collection is null")]
public async Task ParallelForEach_Completes_WhenCollectionNull()
{
await ExecuteAndAssertStatus(null, ActivityStatus.Completed);
}
[Fact(DisplayName = "ParallelForEach executes all items when one faults")]
public async Task ParallelForEach_ExecutesAllItems_WhenOneFaults()
{
var body = new Sequence
{
Activities =
[
new If(context => context.GetVariable<string>(CurrentValueVar) == "b")
{
Then = new Fault { Message = new("Faulted") },
},
WriteCurrentValue()
]
};
await RunActivityAsync(ThreeItems, body);
Assert.Contains("a", CapturingTextWriter.Lines);
Assert.DoesNotContain("b", CapturingTextWriter.Lines);
Assert.Contains("c", CapturingTextWriter.Lines);
}
[Fact(DisplayName = "ParallelForEach executes body for different item types")]
public async Task ParallelForEach_ExecutesBody_ForDifferentItemTypes()
{
var items = new object?[]
{
"a", 2, null, new Foo()
};
var parallelForEach = new ParallelForEach<object?>(items)
{
Body = new WriteLine(context => context.GetVariable<object>(CurrentValueVar)?.ToString() ?? "")
};
await _fixture.RunActivityAsync(parallelForEach);
Assert.Equal(items.Length, CapturingTextWriter.Lines.Count);
Assert.Contains("a", CapturingTextWriter.Lines);
Assert.Contains("2", CapturingTextWriter.Lines);
Assert.Contains("", CapturingTextWriter.Lines);
Assert.Contains("Baz", CapturingTextWriter.Lines);
}
[Fact(DisplayName = "ParallelForEach provides CurrentIndex variable")]
public async Task ParallelForEach_ProvidesCurrentIndex_ForEachIteration()
{
var body = new WriteLine(context => context.GetVariable<int>("CurrentIndex").ToString());
await RunActivityAsync(ThreeItems, body);
Assert.Equal(ThreeItems.Length, CapturingTextWriter.Lines.Count);
Assert.Contains("0", CapturingTextWriter.Lines);
Assert.Contains("1", CapturingTextWriter.Lines);
Assert.Contains("2", CapturingTextWriter.Lines);
}
[Fact(DisplayName = "ParallelForEach completes when all bodies complete")]
public async Task ParallelForEach_Completes_WhenAllBodiesComplete()
{
await ExecuteAndAssertStatus(ThreeItems, ActivityStatus.Completed);
}
public static TheoryData<string[]> ItemTestCases =>
[
ThreeItems,
["single"]
];
private async Task ExecuteAndAssertAllItems(string[] items)
{
await RunActivityAsync(items, WriteCurrentValue());
Assert.Equal(items.Length, CapturingTextWriter.Lines.Count);
Assert.All(items, item => Assert.Contains(item, CapturingTextWriter.Lines));
}
private async Task ExecuteAndAssertStatus(string[]? items, ActivityStatus expectedStatus)
{
var result = await RunActivityAsync(items, WriteCurrentValue());
var context = GetParallelForEachContext(result);
Assert.Equal(expectedStatus, context.Status);
}
private async Task<RunWorkflowResult> RunActivityAsync(string[]? items, IActivity body)
{
var parallelForEach = new ParallelForEach<string>(items!) { Body = body };
return await _fixture.RunActivityAsync(parallelForEach);
}
private static WriteLine WriteCurrentValue() => new(context => context.GetVariable<string>(CurrentValueVar));
private static ActivityExecutionContext GetParallelForEachContext(RunWorkflowResult result)
{
var context = result.Journal.ActivityExecutionContexts.FirstOrDefault(x => x.Activity is ParallelForEach<string>);
Assert.NotNull(context);
return context;
}
}