diff --git a/doc/qa/test-guidelines.md b/doc/qa/test-guidelines.md
index 81feb0519..e97edc817 100644
--- a/doc/qa/test-guidelines.md
+++ b/doc/qa/test-guidelines.md
@@ -195,7 +195,6 @@ public async Task Should_Return_Default_Outcome()
Assert.True(context.HasOutcome("Default"));
}
```
-
#### **Integration tests:**
- Place the activity inside a minimal workflow definition and run via [`IWorkflowRunner.RunAsync`](../../src/modules/Elsa.Workflows.Core/Contracts/IWorkflowRunner.cs). Assert outputs/variables and that the activity integrates correctly with preceding/following activities.
- If activity creates bookmarks or relies on scheduler semantics, integration tests should resume bookmarks via the engine APIs to validate resumption.
diff --git a/src/common/Elsa.Testing.Shared/ActivityTestFixture.cs b/src/common/Elsa.Testing.Shared/ActivityTestFixture.cs
index 0b077d1c9..630698f29 100644
--- a/src/common/Elsa.Testing.Shared/ActivityTestFixture.cs
+++ b/src/common/Elsa.Testing.Shared/ActivityTestFixture.cs
@@ -128,12 +128,15 @@ public class ActivityTestFixture
{
var activityType = activity.GetType();
var variableProperties = activityType.GetProperties()
- .Where(p => p.PropertyType.IsGenericType &&
- p.PropertyType.GetGenericTypeDefinition() == typeof(Variable<>))
+ .Where(p => typeof(Variable).IsAssignableFrom(p.PropertyType))
.ToList();
- foreach (var variable in variableProperties.Select(property => (Variable)property.GetValue(activity)!))
+ foreach (var variable in variableProperties.Select(property => (Variable?)property.GetValue(activity)))
{
+ if(variable == null)
+ continue;
+
+ context.WorkflowExecutionContext.MemoryRegister.Declare(variable);
variable.Set(context.ExpressionExecutionContext, variable.Value);
}
diff --git a/src/modules/Elsa.Workflows.Core/Activities/Flowchart/Activities/FlowJoin.cs b/src/modules/Elsa.Workflows.Core/Activities/Flowchart/Activities/FlowJoin.cs
index a3dacfb73..c41419322 100644
--- a/src/modules/Elsa.Workflows.Core/Activities/Flowchart/Activities/FlowJoin.cs
+++ b/src/modules/Elsa.Workflows.Core/Activities/Flowchart/Activities/FlowJoin.cs
@@ -14,9 +14,8 @@ namespace Elsa.Workflows.Activities.Flowchart.Activities;
/// Note that this activity is no longer necessary for either AND or OR merges, because all activities inherit the Join Kind property.
/// Use this activity if an explicit join step is desired.
///
-[Activity("Elsa", "Branching", "[Obsolete] - Explicitly merge multiple branches into a single branch of execution.", DisplayName = "Join")]
+[Activity("Elsa", "Branching", "Explicitly merge multiple branches into a single branch of execution.", DisplayName = "Join")]
[UsedImplicitly]
-[Obsolete("Each activity now supports the MergeMode property, making the use of this activity obsolete.", false)]
public class FlowJoin : Activity, IJoinNode
{
///
@@ -38,8 +37,9 @@ public class FlowJoin : Activity, IJoinNode
protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
{
if(!Flowchart.UseTokenFlow)
- await context.ParentActivityExecutionContext.CancelInboundAncestorsAsync(this);
-
+ if (context.ParentActivityExecutionContext != null)
+ await context.ParentActivityExecutionContext.CancelInboundAncestorsAsync(this);
+
await context.CompleteActivityAsync();
}
diff --git a/src/modules/Elsa.Workflows.Core/Activities/Flowchart/Extensions/ActivityExtensions.cs b/src/modules/Elsa.Workflows.Core/Activities/Flowchart/Extensions/ActivityExtensions.cs
index 3e26ce616..176126183 100644
--- a/src/modules/Elsa.Workflows.Core/Activities/Flowchart/Extensions/ActivityExtensions.cs
+++ b/src/modules/Elsa.Workflows.Core/Activities/Flowchart/Extensions/ActivityExtensions.cs
@@ -8,8 +8,16 @@ public static class ActivityExtensions
{
public static MergeMode? GetMergeMode(this IActivity activity)
{
- activity.CustomProperties.TryGetValue("mergeMode", out var mergeModeString);
- return Enum.TryParse((string?)mergeModeString, true, out var mergeMode) ? mergeMode : null;
+ if (!activity.CustomProperties.TryGetValue("mergeMode", out var value))
+ return null;
+
+ // Handle both string and enum values for backwards compatibility
+ return value switch
+ {
+ MergeMode mode => mode,
+ string str when Enum.TryParse(str, true, out var mode) => mode,
+ _ => null
+ };
}
public static void SetMergeMode(this IActivity activity, MergeMode? value)
@@ -17,7 +25,7 @@ public static class ActivityExtensions
if (value == null)
activity.CustomProperties.Remove("mergeMode");
else
- activity.CustomProperties["mergeMode"] = value;
+ activity.CustomProperties["mergeMode"] = value.ToString()!;
}
public static async Task GetMergeModeAsync(this IActivity activity, ActivityExecutionContext context)
diff --git a/test/integration/Elsa.Activities.IntegrationTests/Flow/FlowchartCounterBasedTests.cs b/test/integration/Elsa.Activities.IntegrationTests/Flow/FlowchartCounterBasedTests.cs
new file mode 100644
index 000000000..d38200035
--- /dev/null
+++ b/test/integration/Elsa.Activities.IntegrationTests/Flow/FlowchartCounterBasedTests.cs
@@ -0,0 +1,353 @@
+using Elsa.Testing.Shared;
+using Elsa.Workflows;
+using Elsa.Workflows.Activities;
+using Elsa.Workflows.Activities.Flowchart.Activities;
+using Elsa.Workflows.Activities.Flowchart.Models;
+using Xunit.Abstractions;
+using static Elsa.Activities.IntegrationTests.Flow.FlowchartTestHelpers;
+
+namespace Elsa.Activities.IntegrationTests.Flow;
+
+///
+/// Integration tests for counter-based flowchart execution strategy.
+///
+[Collection("FlowchartTests")]
+public class FlowchartCounterBasedTests : IDisposable
+{
+ private readonly IServiceProvider _services;
+ private readonly CapturingTextWriter _output;
+ private readonly bool _originalFlowMode;
+
+ public FlowchartCounterBasedTests(ITestOutputHelper testOutputHelper)
+ {
+ _output = new();
+ _services = CreateServiceProvider(testOutputHelper, _output);
+ _originalFlowMode = Flowchart.UseTokenFlow;
+ Flowchart.UseTokenFlow = false;
+ }
+
+ public void Dispose()
+ {
+ Flowchart.UseTokenFlow = _originalFlowMode;
+ }
+
+ [Fact(DisplayName = "Executes simple linear flowchart")]
+ public async Task ExecutesSimpleLinearFlowchart()
+ {
+ // Arrange
+ var flowchart = CreateSimpleLinearFlowchart(
+ new WriteLine("First"),
+ new WriteLine("Second"),
+ new WriteLine("Third")
+ );
+
+ // Act
+ await RunFlowchartAsync(_services, flowchart);
+
+ // Assert
+ Assert.Equal(3, _output.Lines.Count);
+ Assert.Equal("First", _output.Lines.ElementAt(0));
+ Assert.Equal("Second", _output.Lines.ElementAt(1));
+ Assert.Equal("Third", _output.Lines.ElementAt(2));
+ }
+
+ [Fact(DisplayName = "Executes both branches in parallel flowchart")]
+ public async Task ExecutesBothBranches()
+ {
+ // Arrange
+ var start = new WriteLine("Start");
+ var branch1 = new WriteLine("Branch1");
+ var branch2 = new WriteLine("Branch2");
+ var flowchart = CreateBranchingFlowchart(start, branch1, branch2);
+
+ // Act
+ await RunFlowchartAsync(_services, flowchart);
+
+ // Assert
+ Assert.Equal(3, _output.Lines.Count);
+ Assert.Contains("Start", _output.Lines);
+ Assert.Contains("Branch1", _output.Lines);
+ Assert.Contains("Branch2", _output.Lines);
+ }
+
+ [Fact(DisplayName = "Handles flowchart with no connections")]
+ public async Task HandlesNoConnections()
+ {
+ // Arrange
+ var activity = new WriteLine("Isolated");
+ var flowchart = new Flowchart
+ {
+ Start = activity,
+ Activities = { activity }
+ };
+
+ // Act
+ await RunFlowchartAsync(_services, flowchart);
+
+ // Assert
+ Assert.Single(_output.Lines);
+ Assert.Equal("Isolated", _output.Lines.ElementAt(0));
+ }
+
+ [Fact(DisplayName = "Completes when start activity is null")]
+ public async Task CompletesWhenStartIsNull()
+ {
+ // Arrange
+ var flowchart = new Flowchart
+ {
+ Start = null
+ };
+
+ // Act
+ var result = await RunFlowchartAsync(_services, flowchart);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Empty(_output.Lines);
+ }
+
+ [Fact(DisplayName = "Follows conditional branches with If activity")]
+ public async Task FollowsConditionalBranches()
+ {
+ // Arrange
+ var ifActivity = new If
+ {
+ Condition = new(true),
+ Then = new WriteLine("Then branch"),
+ Else = new WriteLine("Else branch")
+ };
+ var flowchart = new Flowchart
+ {
+ Start = ifActivity,
+ Activities = { ifActivity }
+ };
+
+ // Act
+ await RunFlowchartAsync(_services, flowchart);
+
+ // Assert
+ Assert.Single(_output.Lines);
+ Assert.Equal("Then branch", _output.Lines.ElementAt(0));
+ }
+
+ [Fact(DisplayName = "Executes join node with WaitAny mode")]
+ public async Task ExecutesJoinNodeWaitAny()
+ {
+ // Arrange
+ var start = new WriteLine("Start");
+ var branch1 = new WriteLine("Branch1");
+ var branch2 = new WriteLine("Branch2");
+ var join = new FlowJoin { Mode = new(FlowJoinMode.WaitAny) };
+ var afterJoin = new WriteLine("AfterJoin");
+
+ var flowchart = new Flowchart
+ {
+ Start = start,
+ Activities = { start, branch1, branch2, join, afterJoin },
+ Connections =
+ {
+ CreateConnection(start, branch1),
+ CreateConnection(start, branch2),
+ CreateConnection(branch1, join),
+ CreateConnection(branch2, join),
+ CreateConnection(join, afterJoin)
+ }
+ };
+
+ // Act
+ await RunFlowchartAsync(_services, flowchart);
+
+ // Assert
+ Assert.Contains("Start", _output.Lines);
+ Assert.Contains("AfterJoin", _output.Lines);
+ // At least one branch should execute
+ Assert.True(_output.Lines.Contains("Branch1") || _output.Lines.Contains("Branch2"));
+ }
+
+ [Fact(DisplayName = "Executes join node with WaitAll mode")]
+ public async Task ExecutesJoinNodeWaitAll()
+ {
+ // Arrange
+ var start = new WriteLine("Start");
+ var branch1 = new WriteLine("Branch1");
+ var branch2 = new WriteLine("Branch2");
+ var join = new FlowJoin { Mode = new(FlowJoinMode.WaitAll) };
+ var afterJoin = new WriteLine("AfterJoin");
+
+ var flowchart = new Flowchart
+ {
+ Start = start,
+ Activities = { start, branch1, branch2, join, afterJoin },
+ Connections =
+ {
+ CreateConnection(start, branch1),
+ CreateConnection(start, branch2),
+ CreateConnection(branch1, join),
+ CreateConnection(branch2, join),
+ CreateConnection(join, afterJoin)
+ }
+ };
+
+ // Act
+ await RunFlowchartAsync(_services, flowchart);
+
+ // Assert
+ Assert.Contains("Start", _output.Lines);
+ Assert.Contains("Branch1", _output.Lines);
+ Assert.Contains("Branch2", _output.Lines);
+ Assert.Contains("AfterJoin", _output.Lines);
+ }
+
+ [Fact(DisplayName = "Handles multiple sequential joins")]
+ public async Task HandlesMultipleSequentialJoins()
+ {
+ // Arrange
+ var start = new WriteLine("Start");
+ var a1 = new WriteLine("A1");
+ var a2 = new WriteLine("A2");
+ var join1 = new FlowJoin { Mode = new(FlowJoinMode.WaitAll) };
+ var b1 = new WriteLine("B1");
+ var b2 = new WriteLine("B2");
+ var join2 = new FlowJoin { Mode = new(FlowJoinMode.WaitAll) };
+ var end = new WriteLine("End");
+
+ var flowchart = new Flowchart
+ {
+ Start = start,
+ Activities = { start, a1, a2, join1, b1, b2, join2, end },
+ Connections =
+ {
+ CreateConnection(start, a1),
+ CreateConnection(start, a2),
+ CreateConnection(a1, join1),
+ CreateConnection(a2, join1),
+ CreateConnection(join1, b1),
+ CreateConnection(join1, b2),
+ CreateConnection(b1, join2),
+ CreateConnection(b2, join2),
+ CreateConnection(join2, end)
+ }
+ };
+
+ // Act
+ await RunFlowchartAsync(_services, flowchart);
+
+ // Assert
+ Assert.Contains("Start", _output.Lines);
+ Assert.Contains("A1", _output.Lines);
+ Assert.Contains("A2", _output.Lines);
+ Assert.Contains("B1", _output.Lines);
+ Assert.Contains("B2", _output.Lines);
+ Assert.Contains("End", _output.Lines);
+ }
+
+ [Fact(DisplayName = "Handles complex diamond pattern")]
+ public async Task HandlesComplexDiamondPattern()
+ {
+ // Arrange
+ var start = new WriteLine("Start");
+ var left1 = new WriteLine("Left1");
+ var left2 = new WriteLine("Left2");
+ var right1 = new WriteLine("Right1");
+ var right2 = new WriteLine("Right2");
+ var join = new FlowJoin { Mode = new(FlowJoinMode.WaitAll) };
+ var end = new WriteLine("End");
+
+ var flowchart = new Flowchart
+ {
+ Start = start,
+ Activities = { start, left1, left2, right1, right2, join, end },
+ Connections =
+ {
+ CreateConnection(start, left1),
+ CreateConnection(start, right1),
+ CreateConnection(left1, left2),
+ CreateConnection(right1, right2),
+ CreateConnection(left2, join),
+ CreateConnection(right2, join),
+ CreateConnection(join, end)
+ }
+ };
+
+ // Act
+ await RunFlowchartAsync(_services, flowchart);
+
+ // Assert
+ Assert.Contains("Start", _output.Lines);
+ Assert.Contains("Left1", _output.Lines);
+ Assert.Contains("Left2", _output.Lines);
+ Assert.Contains("Right1", _output.Lines);
+ Assert.Contains("Right2", _output.Lines);
+ Assert.Contains("End", _output.Lines);
+ }
+
+ [Fact(DisplayName = "Executes activities in correct order for sequential flow")]
+ public async Task ExecutesInCorrectOrderForSequential()
+ {
+ // Arrange
+ var flowchart = CreateSimpleLinearFlowchart(
+ new WriteLine("1"),
+ new WriteLine("2"),
+ new WriteLine("3"),
+ new WriteLine("4")
+ );
+
+ // Act
+ await RunFlowchartAsync(_services, flowchart);
+
+ // Assert
+ Assert.Equal(4, _output.Lines.Count);
+ Assert.Equal("1", _output.Lines.ElementAt(0));
+ Assert.Equal("2", _output.Lines.ElementAt(1));
+ Assert.Equal("3", _output.Lines.ElementAt(2));
+ Assert.Equal("4", _output.Lines.ElementAt(3));
+ }
+
+ [Fact(DisplayName = "Handles nested flowcharts")]
+ public async Task HandlesNestedFlowcharts()
+ {
+ // Arrange
+ var innerFlowchart = CreateSimpleLinearFlowchart(
+ new WriteLine("Inner1"),
+ new WriteLine("Inner2")
+ );
+
+ var outerFlowchart = CreateSimpleLinearFlowchart(
+ new WriteLine("Outer1"),
+ innerFlowchart,
+ new WriteLine("Outer2")
+ );
+
+ // Act
+ await RunFlowchartAsync(_services, outerFlowchart);
+
+ // Assert
+ Assert.Contains("Outer1", _output.Lines);
+ Assert.Contains("Inner1", _output.Lines);
+ Assert.Contains("Inner2", _output.Lines);
+ Assert.Contains("Outer2", _output.Lines);
+ }
+
+ [Fact(DisplayName = "Handles unconnected activities in flowchart")]
+ public async Task HandlesUnconnectedActivities()
+ {
+ // Arrange
+ var connected = new WriteLine("Connected");
+ var unconnected = new WriteLine("Unconnected");
+
+ var flowchart = new Flowchart
+ {
+ Start = connected,
+ Activities = { connected, unconnected }
+ // No connection to unconnected activity
+ };
+
+ // Act
+ await RunFlowchartAsync(_services, flowchart);
+
+ // Assert
+ Assert.Single(_output.Lines);
+ Assert.Equal("Connected", _output.Lines.ElementAt(0));
+ Assert.DoesNotContain("Unconnected", _output.Lines);
+ }
+}
diff --git a/test/integration/Elsa.Activities.IntegrationTests/Flow/FlowchartTestCollection.cs b/test/integration/Elsa.Activities.IntegrationTests/Flow/FlowchartTestCollection.cs
new file mode 100644
index 000000000..c5c0c1431
--- /dev/null
+++ b/test/integration/Elsa.Activities.IntegrationTests/Flow/FlowchartTestCollection.cs
@@ -0,0 +1,10 @@
+namespace Elsa.Activities.IntegrationTests.Flow;
+
+///
+/// Defines a test collection to ensure flowchart tests don't run in parallel.
+/// This is necessary because the tests modify the process-wide static Flowchart.UseTokenFlow flag.
+///
+[CollectionDefinition("FlowchartTests", DisableParallelization = true)]
+public class FlowchartTestCollection
+{
+}
diff --git a/test/integration/Elsa.Activities.IntegrationTests/Flow/FlowchartTestHelpers.cs b/test/integration/Elsa.Activities.IntegrationTests/Flow/FlowchartTestHelpers.cs
new file mode 100644
index 000000000..c5ee60959
--- /dev/null
+++ b/test/integration/Elsa.Activities.IntegrationTests/Flow/FlowchartTestHelpers.cs
@@ -0,0 +1,69 @@
+using Elsa.Testing.Shared;
+using Elsa.Workflows;
+using Elsa.Workflows.Activities.Flowchart.Activities;
+using Elsa.Workflows.Activities.Flowchart.Models;
+using Elsa.Workflows.Models;
+using Xunit.Abstractions;
+
+namespace Elsa.Activities.IntegrationTests.Flow;
+
+///
+/// Shared helper methods for Flowchart integration tests.
+///
+public static class FlowchartTestHelpers
+{
+ public static IServiceProvider CreateServiceProvider(ITestOutputHelper testOutputHelper, CapturingTextWriter? capturingTextWriter = null)
+ {
+ var builder = new TestApplicationBuilder(testOutputHelper);
+ if (capturingTextWriter != null)
+ builder.WithCapturingTextWriter(capturingTextWriter);
+ return builder.Build();
+ }
+
+ public static async Task RunFlowchartAsync(IServiceProvider services, Flowchart flowchart)
+ {
+ return await services.RunActivityAsync(flowchart);
+ }
+
+ public static Connection CreateConnection(IActivity source, IActivity target, string? outcome = "Done")
+ {
+ return new(new(source, outcome), new Endpoint(target));
+ }
+
+ public static Flowchart CreateSimpleLinearFlowchart(params IActivity[] activities)
+ {
+ var flowchart = new Flowchart
+ {
+ Start = activities.FirstOrDefault(),
+ Activities = new List(activities)
+ };
+
+ for (var i = 0; i < activities.Length - 1; i++)
+ {
+ flowchart.Connections.Add(CreateConnection(activities[i], activities[i + 1]));
+ }
+
+ return flowchart;
+ }
+
+ public static Flowchart CreateBranchingFlowchart(IActivity start, IActivity branch1, IActivity branch2, IActivity? join = null)
+ {
+ var flowchart = new Flowchart
+ {
+ Start = start,
+ Activities = { start, branch1, branch2 }
+ };
+
+ flowchart.Connections.Add(CreateConnection(start, branch1));
+ flowchart.Connections.Add(CreateConnection(start, branch2));
+
+ if (join == null)
+ return flowchart;
+
+ flowchart.Activities.Add(join);
+ flowchart.Connections.Add(CreateConnection(branch1, join));
+ flowchart.Connections.Add(CreateConnection(branch2, join));
+
+ return flowchart;
+ }
+}
diff --git a/test/integration/Elsa.Activities.IntegrationTests/Flow/FlowchartTokenBasedTests.cs b/test/integration/Elsa.Activities.IntegrationTests/Flow/FlowchartTokenBasedTests.cs
new file mode 100644
index 000000000..d1fadbf85
--- /dev/null
+++ b/test/integration/Elsa.Activities.IntegrationTests/Flow/FlowchartTokenBasedTests.cs
@@ -0,0 +1,549 @@
+using Elsa.Testing.Shared;
+using Elsa.Workflows;
+using Elsa.Workflows.Activities;
+using Elsa.Workflows.Activities.Flowchart.Activities;
+using Elsa.Workflows.Activities.Flowchart.Extensions;
+using Elsa.Workflows.Activities.Flowchart.Models;
+using Xunit.Abstractions;
+using static Elsa.Activities.IntegrationTests.Flow.FlowchartTestHelpers;
+
+namespace Elsa.Activities.IntegrationTests.Flow;
+
+///
+/// Integration tests for token-based flowchart execution strategy.
+///
+[Collection("FlowchartTests")]
+public class FlowchartTokenBasedTests : IDisposable
+{
+ private readonly IServiceProvider _services;
+ private readonly CapturingTextWriter _output;
+ private readonly bool _originalFlowMode;
+
+ public FlowchartTokenBasedTests(ITestOutputHelper testOutputHelper)
+ {
+ _output = new();
+ _services = CreateServiceProvider(testOutputHelper, _output);
+ _originalFlowMode = Flowchart.UseTokenFlow;
+ Flowchart.UseTokenFlow = true;
+ }
+
+ public void Dispose()
+ {
+ Flowchart.UseTokenFlow = _originalFlowMode;
+ }
+
+ [Fact(DisplayName = "Executes simple linear flowchart")]
+ public async Task ExecutesSimpleLinearFlowchart()
+ {
+ // Arrange
+ var flowchart = CreateSimpleLinearFlowchart(
+ new WriteLine("First"),
+ new WriteLine("Second"),
+ new WriteLine("Third")
+ );
+
+ // Act
+ await RunFlowchartAsync(_services, flowchart);
+
+ // Assert
+ Assert.Equal(3, _output.Lines.Count);
+ Assert.Equal("First", _output.Lines.ElementAt(0));
+ Assert.Equal("Second", _output.Lines.ElementAt(1));
+ Assert.Equal("Third", _output.Lines.ElementAt(2));
+ }
+
+ [Fact(DisplayName = "Executes both branches in parallel flowchart")]
+ public async Task ExecutesBothBranches()
+ {
+ // Arrange
+ var start = new WriteLine("Start");
+ var branch1 = new WriteLine("Branch1");
+ var branch2 = new WriteLine("Branch2");
+ var flowchart = CreateBranchingFlowchart(start, branch1, branch2);
+
+ // Act
+ await RunFlowchartAsync(_services, flowchart);
+
+ // Assert
+ Assert.Equal(3, _output.Lines.Count);
+ Assert.Contains("Start", _output.Lines);
+ Assert.Contains("Branch1", _output.Lines);
+ Assert.Contains("Branch2", _output.Lines);
+ }
+
+ [Fact(DisplayName = "Handles flowchart with no connections")]
+ public async Task HandlesNoConnections()
+ {
+ // Arrange
+ var activity = new WriteLine("Isolated");
+ var flowchart = new Flowchart
+ {
+ Start = activity,
+ Activities = { activity }
+ };
+
+ // Act
+ await RunFlowchartAsync(_services, flowchart);
+
+ // Assert
+ Assert.Single(_output.Lines);
+ Assert.Equal("Isolated", _output.Lines.ElementAt(0));
+ }
+
+ [Fact(DisplayName = "Completes when start activity is null")]
+ public async Task CompletesWhenStartIsNull()
+ {
+ // Arrange
+ var flowchart = new Flowchart
+ {
+ Start = null
+ };
+
+ // Act
+ var result = await RunFlowchartAsync(_services, flowchart);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Empty(_output.Lines);
+ }
+
+ [Fact(DisplayName = "Follows conditional branches with If activity")]
+ public async Task FollowsConditionalBranches()
+ {
+ // Arrange
+ var ifActivity = new If
+ {
+ Condition = new(true),
+ Then = new WriteLine("Then branch"),
+ Else = new WriteLine("Else branch")
+ };
+ var flowchart = new Flowchart
+ {
+ Start = ifActivity,
+ Activities = { ifActivity }
+ };
+
+ // Act
+ await RunFlowchartAsync(_services, flowchart);
+
+ // Assert
+ Assert.Single(_output.Lines);
+ Assert.Equal("Then branch", _output.Lines.ElementAt(0));
+ }
+
+ [Fact(DisplayName = "Executes Stream merge mode - schedules immediately")]
+ public async Task ExecutesStreamMergeMode()
+ {
+ // Arrange
+ var start = new WriteLine("Start");
+ var branch1 = new WriteLine("Branch1");
+ var branch2 = new WriteLine("Branch2");
+ var afterJoin = new WriteLine("AfterJoin");
+ afterJoin.SetMergeMode(MergeMode.Stream);
+
+ var flowchart = new Flowchart
+ {
+ Start = start,
+ Activities = { start, branch1, branch2, afterJoin },
+ Connections =
+ {
+ CreateConnection(start, branch1),
+ CreateConnection(start, branch2),
+ CreateConnection(branch1, afterJoin),
+ CreateConnection(branch2, afterJoin)
+ }
+ };
+
+ // Act
+ await RunFlowchartAsync(_services, flowchart);
+
+ // Assert
+ Assert.Contains("Start", _output.Lines);
+ Assert.Contains("AfterJoin", _output.Lines);
+ // In Stream mode, afterJoin executes as soon as first branch arrives
+ }
+
+ [Fact(DisplayName = "Executes Race merge mode - cancels other branches")]
+ public async Task ExecutesRaceMergeMode()
+ {
+ // Arrange
+ var start = new WriteLine("Start");
+ var branch1 = new WriteLine("Branch1");
+ var branch2 = new WriteLine("Branch2");
+ var afterRace = new WriteLine("AfterRace");
+ afterRace.SetMergeMode(MergeMode.Race);
+
+ var flowchart = new Flowchart
+ {
+ Start = start,
+ Activities = { start, branch1, branch2, afterRace },
+ Connections =
+ {
+ CreateConnection(start, branch1),
+ CreateConnection(start, branch2),
+ CreateConnection(branch1, afterRace),
+ CreateConnection(branch2, afterRace)
+ }
+ };
+
+ // Act
+ await RunFlowchartAsync(_services, flowchart);
+
+ // Assert
+ Assert.Contains("Start", _output.Lines);
+ Assert.Contains("AfterRace", _output.Lines);
+ // In Race mode, afterRace executes on first arrival and blocks others
+ }
+
+ [Fact(DisplayName = "Executes Converge merge mode - waits for all branches")]
+ public async Task ExecutesConvergeMergeMode()
+ {
+ // Arrange
+ var start = new WriteLine("Start");
+ var branch1 = new WriteLine("Branch1");
+ var branch2 = new WriteLine("Branch2");
+ var converge = new WriteLine("Converge");
+ converge.SetMergeMode(MergeMode.Converge);
+ var end = new WriteLine("End");
+
+ var flowchart = new Flowchart
+ {
+ Start = start,
+ Activities = { start, branch1, branch2, converge, end },
+ Connections =
+ {
+ CreateConnection(start, branch1),
+ CreateConnection(start, branch2),
+ CreateConnection(branch1, converge),
+ CreateConnection(branch2, converge),
+ CreateConnection(converge, end)
+ }
+ };
+
+ // Act
+ await RunFlowchartAsync(_services, flowchart);
+
+ // Assert
+ Assert.Contains("Start", _output.Lines);
+ Assert.Contains("Branch1", _output.Lines);
+ Assert.Contains("Branch2", _output.Lines);
+ Assert.Contains("Converge", _output.Lines);
+ Assert.Contains("End", _output.Lines);
+ }
+
+ [Fact(DisplayName = "Executes None merge mode correctly")]
+ public async Task ExecutesNoneMergeMode()
+ {
+ // Arrange
+ var start = new WriteLine("Start");
+ var branch1 = new WriteLine("Branch1");
+ var branch2 = new WriteLine("Branch2");
+ var noneMode = new WriteLine("NoneMode");
+ noneMode.SetMergeMode(MergeMode.None);
+
+ var flowchart = new Flowchart
+ {
+ Start = start,
+ Activities = { start, branch1, branch2, noneMode },
+ Connections =
+ {
+ CreateConnection(start, branch1),
+ CreateConnection(start, branch2),
+ CreateConnection(branch1, noneMode),
+ CreateConnection(branch2, noneMode)
+ }
+ };
+
+ // Act
+ await RunFlowchartAsync(_services, flowchart);
+
+ // Assert
+ Assert.Contains("Start", _output.Lines);
+ Assert.Contains("Branch1", _output.Lines);
+ Assert.Contains("Branch2", _output.Lines);
+ Assert.Contains("NoneMode", _output.Lines);
+ }
+
+ [Fact(DisplayName = "Handles token consumption correctly")]
+ public async Task HandlesTokenConsumption()
+ {
+ // Arrange
+ var start = new WriteLine("Start");
+ var middle = new WriteLine("Middle");
+ var end = new WriteLine("End");
+
+ var flowchart = CreateSimpleLinearFlowchart(start, middle, end);
+
+ // Act
+ await RunFlowchartAsync(_services, flowchart);
+
+ // Assert
+ // Tokens should be consumed after each activity completes
+ Assert.Equal(3, _output.Lines.Count);
+ Assert.Equal("Start", _output.Lines.ElementAt(0));
+ Assert.Equal("Middle", _output.Lines.ElementAt(1));
+ Assert.Equal("End", _output.Lines.ElementAt(2));
+ }
+
+ [Fact(DisplayName = "Handles multiple sequential converge nodes")]
+ public async Task HandlesMultipleSequentialConvergeNodes()
+ {
+ // Arrange
+ var start = new WriteLine("Start");
+ var a1 = new WriteLine("A1");
+ var a2 = new WriteLine("A2");
+ var converge1 = new WriteLine("Converge1");
+ converge1.SetMergeMode(MergeMode.Converge);
+ var b1 = new WriteLine("B1");
+ var b2 = new WriteLine("B2");
+ var converge2 = new WriteLine("Converge2");
+ converge2.SetMergeMode(MergeMode.Converge);
+ var end = new WriteLine("End");
+
+ var flowchart = new Flowchart
+ {
+ Start = start,
+ Activities = { start, a1, a2, converge1, b1, b2, converge2, end },
+ Connections =
+ {
+ CreateConnection(start, a1),
+ CreateConnection(start, a2),
+ CreateConnection(a1, converge1),
+ CreateConnection(a2, converge1),
+ CreateConnection(converge1, b1),
+ CreateConnection(converge1, b2),
+ CreateConnection(b1, converge2),
+ CreateConnection(b2, converge2),
+ CreateConnection(converge2, end)
+ }
+ };
+
+ // Act
+ await RunFlowchartAsync(_services, flowchart);
+
+ // Assert
+ Assert.Contains("Start", _output.Lines);
+ Assert.Contains("A1", _output.Lines);
+ Assert.Contains("A2", _output.Lines);
+ Assert.Contains("Converge1", _output.Lines);
+ Assert.Contains("B1", _output.Lines);
+ Assert.Contains("B2", _output.Lines);
+ Assert.Contains("Converge2", _output.Lines);
+ Assert.Contains("End", _output.Lines);
+ }
+
+ [Fact(DisplayName = "Handles complex diamond pattern with tokens")]
+ public async Task HandlesComplexDiamondPattern()
+ {
+ // Arrange
+ var start = new WriteLine("Start");
+ var left1 = new WriteLine("Left1");
+ var left2 = new WriteLine("Left2");
+ var right1 = new WriteLine("Right1");
+ var right2 = new WriteLine("Right2");
+ var converge = new WriteLine("Converge");
+ converge.SetMergeMode(MergeMode.Converge);
+ var end = new WriteLine("End");
+
+ var flowchart = new Flowchart
+ {
+ Start = start,
+ Activities = { start, left1, left2, right1, right2, converge, end },
+ Connections =
+ {
+ CreateConnection(start, left1),
+ CreateConnection(start, right1),
+ CreateConnection(left1, left2),
+ CreateConnection(right1, right2),
+ CreateConnection(left2, converge),
+ CreateConnection(right2, converge),
+ CreateConnection(converge, end)
+ }
+ };
+
+ // Act
+ await RunFlowchartAsync(_services, flowchart);
+
+ // Assert
+ Assert.Contains("Start", _output.Lines);
+ Assert.Contains("Left1", _output.Lines);
+ Assert.Contains("Left2", _output.Lines);
+ Assert.Contains("Right1", _output.Lines);
+ Assert.Contains("Right2", _output.Lines);
+ Assert.Contains("Converge", _output.Lines);
+ Assert.Contains("End", _output.Lines);
+ }
+
+ [Fact(DisplayName = "Executes activities in correct order for sequential flow")]
+ public async Task ExecutesInCorrectOrderForSequential()
+ {
+ // Arrange
+ var flowchart = CreateSimpleLinearFlowchart(
+ new WriteLine("1"),
+ new WriteLine("2"),
+ new WriteLine("3"),
+ new WriteLine("4")
+ );
+
+ // Act
+ await RunFlowchartAsync(_services, flowchart);
+
+ // Assert
+ Assert.Equal(4, _output.Lines.Count);
+ Assert.Equal("1", _output.Lines.ElementAt(0));
+ Assert.Equal("2", _output.Lines.ElementAt(1));
+ Assert.Equal("3", _output.Lines.ElementAt(2));
+ Assert.Equal("4", _output.Lines.ElementAt(3));
+ }
+
+ [Fact(DisplayName = "Handles nested flowcharts with tokens")]
+ public async Task HandlesNestedFlowcharts()
+ {
+ // Arrange
+ var innerFlowchart = CreateSimpleLinearFlowchart(
+ new WriteLine("Inner1"),
+ new WriteLine("Inner2")
+ );
+
+ var outerFlowchart = CreateSimpleLinearFlowchart(
+ new WriteLine("Outer1"),
+ innerFlowchart,
+ new WriteLine("Outer2")
+ );
+
+ // Act
+ await RunFlowchartAsync(_services, outerFlowchart);
+
+ // Assert
+ Assert.Contains("Outer1", _output.Lines);
+ Assert.Contains("Inner1", _output.Lines);
+ Assert.Contains("Inner2", _output.Lines);
+ Assert.Contains("Outer2", _output.Lines);
+ }
+
+ [Fact(DisplayName = "Handles unconnected activities in flowchart")]
+ public async Task HandlesUnconnectedActivities()
+ {
+ // Arrange
+ var connected = new WriteLine("Connected");
+ var unconnected = new WriteLine("Unconnected");
+
+ var flowchart = new Flowchart
+ {
+ Start = connected,
+ Activities = { connected, unconnected }
+ // No connection to unconnected activity
+ };
+
+ // Act
+ await RunFlowchartAsync(_services, flowchart);
+
+ // Assert
+ Assert.Single(_output.Lines);
+ Assert.Equal("Connected", _output.Lines.ElementAt(0));
+ Assert.DoesNotContain("Unconnected", _output.Lines);
+ }
+
+ [Fact(DisplayName = "Handles mixed merge modes in complex flow")]
+ public async Task HandlesMixedMergeModes()
+ {
+ // Arrange
+ var start = new WriteLine("Start");
+ var branch1 = new WriteLine("Branch1");
+ var branch2 = new WriteLine("Branch2");
+ var stream = new WriteLine("Stream");
+ stream.SetMergeMode(MergeMode.Stream);
+ var branch3 = new WriteLine("Branch3");
+ var branch4 = new WriteLine("Branch4");
+ var converge = new WriteLine("Converge");
+ converge.SetMergeMode(MergeMode.Converge);
+ var end = new WriteLine("End");
+
+ var flowchart = new Flowchart
+ {
+ Start = start,
+ Activities = { start, branch1, branch2, stream, branch3, branch4, converge, end },
+ Connections =
+ {
+ CreateConnection(start, branch1),
+ CreateConnection(start, branch2),
+ CreateConnection(branch1, stream),
+ CreateConnection(branch2, stream),
+ CreateConnection(stream, branch3),
+ CreateConnection(stream, branch4),
+ CreateConnection(branch3, converge),
+ CreateConnection(branch4, converge),
+ CreateConnection(converge, end)
+ }
+ };
+
+ // Act
+ await RunFlowchartAsync(_services, flowchart);
+
+ // Assert
+ Assert.Contains("Start", _output.Lines);
+ Assert.Contains("Stream", _output.Lines);
+ Assert.Contains("Converge", _output.Lines);
+ Assert.Contains("End", _output.Lines);
+ }
+
+ [Fact(DisplayName = "Handles converge with single inbound connection")]
+ public async Task HandlesConvergeWithSingleInbound()
+ {
+ // Arrange
+ var start = new WriteLine("Start");
+ var single = new WriteLine("Single");
+ single.SetMergeMode(MergeMode.Converge);
+ var end = new WriteLine("End");
+
+ var flowchart = CreateSimpleLinearFlowchart(start, single, end);
+
+ // Act
+ await RunFlowchartAsync(_services, flowchart);
+
+ // Assert
+ Assert.Contains("Start", _output.Lines);
+ Assert.Contains("Single", _output.Lines);
+ Assert.Contains("End", _output.Lines);
+ }
+
+ [Fact(DisplayName = "Emits and consumes tokens correctly across multiple steps")]
+ public async Task EmitsAndConsumesTokensCorrectly()
+ {
+ // Arrange
+ var step1 = new WriteLine("Step1");
+ var step2a = new WriteLine("Step2a");
+ var step2b = new WriteLine("Step2b");
+ var step3 = new WriteLine("Step3");
+ step3.SetMergeMode(MergeMode.Converge);
+ var step4 = new WriteLine("Step4");
+
+ var flowchart = new Flowchart
+ {
+ Start = step1,
+ Activities = { step1, step2a, step2b, step3, step4 },
+ Connections =
+ {
+ CreateConnection(step1, step2a),
+ CreateConnection(step1, step2b),
+ CreateConnection(step2a, step3),
+ CreateConnection(step2b, step3),
+ CreateConnection(step3, step4)
+ }
+ };
+
+ // Act
+ await RunFlowchartAsync(_services, flowchart);
+
+ // Assert
+ // Verify all activities executed in a valid order
+ Assert.Contains("Step1", _output.Lines);
+ Assert.Contains("Step2a", _output.Lines);
+ Assert.Contains("Step2b", _output.Lines);
+ Assert.Contains("Step3", _output.Lines);
+ Assert.Contains("Step4", _output.Lines);
+
+ // Step3 should only appear once (tokens consumed properly)
+ Assert.Single(_output.Lines, l => l == "Step3");
+ }
+}
diff --git a/test/integration/Elsa.Activities.IntegrationTests/SetVariableTests.cs b/test/integration/Elsa.Activities.IntegrationTests/SetVariableTests.cs
new file mode 100644
index 000000000..5a302676f
--- /dev/null
+++ b/test/integration/Elsa.Activities.IntegrationTests/SetVariableTests.cs
@@ -0,0 +1,63 @@
+using Elsa.Extensions;
+using Elsa.Testing.Shared;
+using Elsa.Workflows;
+using Elsa.Workflows.Activities;
+using Elsa.Workflows.Memory;
+using Microsoft.Extensions.DependencyInjection;
+using Xunit.Abstractions;
+
+namespace Elsa.Activities.IntegrationTests;
+
+public class SetVariableTests
+{
+ private readonly IWorkflowRunner _workflowRunner;
+ private readonly CapturingTextWriter _capturingTextWriter = new();
+ private readonly IServiceProvider _services;
+
+ public SetVariableTests(ITestOutputHelper testOutputHelper)
+ {
+ _services = new TestApplicationBuilder(testOutputHelper).WithCapturingTextWriter(_capturingTextWriter).Build();
+ _workflowRunner = _services.GetRequiredService();
+ }
+
+ [Fact(DisplayName = "SetVariable sets variable in nearest scope when multiple variables with same name exist")]
+ public async Task SetVariable_SetsVariableInNearestScope_WhenMultipleVariablesWithSameNameExist()
+ {
+ await _services.PopulateRegistriesAsync();
+ await _workflowRunner.RunAsync();
+ var lines = _capturingTextWriter.Lines.ToList();
+
+ // The sequence-level variable should be set to "Sequence Value"
+ Assert.Equal(new[] { "Sequence Value" }, lines);
+ }
+}
+
+class VariableScopingWorkflow : WorkflowBase
+{
+ protected override void Build(IWorkflowBuilder workflow)
+ {
+ var workflowLevelVariable = new Variable("Foo", "Workflow Value");
+ var sequenceLevelVariable = new Variable("Foo", "Initial Value");
+
+ workflow.Root = new Sequence
+ {
+ Variables = { workflowLevelVariable },
+ Activities =
+ {
+ new Sequence
+ {
+ Variables = { sequenceLevelVariable },
+ Activities =
+ {
+ new SetVariable
+ {
+ Variable = sequenceLevelVariable,
+ Value = new("Sequence Value")
+ },
+ new WriteLine(context => context.GetVariable("Foo"))
+ }
+ }
+ }
+ };
+ }
+}
diff --git a/test/unit/Elsa.Activities.UnitTests/Flow/FlowchartTestHelpers.cs b/test/unit/Elsa.Activities.UnitTests/Flow/FlowchartTestHelpers.cs
new file mode 100644
index 000000000..c504f48b7
--- /dev/null
+++ b/test/unit/Elsa.Activities.UnitTests/Flow/FlowchartTestHelpers.cs
@@ -0,0 +1,17 @@
+using Elsa.Testing.Shared;
+using Elsa.Workflows;
+using Elsa.Workflows.Activities.Flowchart.Activities;
+
+namespace Elsa.Activities.UnitTests.Flow;
+
+///
+/// Shared helper methods for Flowchart unit tests.
+///
+public static class FlowchartTestHelpers
+{
+ public static async Task ExecuteFlowchartAsync(Flowchart flowchart)
+ {
+ var fixture = new ActivityTestFixture(flowchart);
+ return await fixture.ExecuteAsync();
+ }
+}
diff --git a/test/unit/Elsa.Activities.UnitTests/Flow/FlowchartTests.cs b/test/unit/Elsa.Activities.UnitTests/Flow/FlowchartTests.cs
new file mode 100644
index 000000000..cbca233e9
--- /dev/null
+++ b/test/unit/Elsa.Activities.UnitTests/Flow/FlowchartTests.cs
@@ -0,0 +1,93 @@
+using Elsa.Testing.Shared;
+using Elsa.Workflows.Activities.Flowchart.Activities;
+using static Elsa.Activities.UnitTests.Flow.FlowchartTestHelpers;
+
+namespace Elsa.Activities.UnitTests.Flow;
+
+///
+/// Tests for common Flowchart behavior (both counter and token-based strategies).
+///
+public class FlowchartTests
+{
+ [Fact(DisplayName = "Schedules start activity when specified")]
+ public async Task SchedulesStartActivity()
+ {
+ // Arrange
+ var startActivity = new WriteLine("Start");
+ var flowchart = new Flowchart
+ {
+ Start = startActivity
+ };
+
+ // Act
+ var context = await ExecuteFlowchartAsync(flowchart);
+
+ // Assert
+ Assert.True(context.HasScheduledActivity(startActivity));
+ }
+
+ [Fact(DisplayName = "Executes without error when no start activity specified")]
+ public async Task ExecutesWithoutErrorWhenNoStartActivity()
+ {
+ // Arrange
+ var flowchart = new Flowchart
+ {
+ Start = null
+ };
+
+ // Act
+ var context = await ExecuteFlowchartAsync(flowchart);
+
+ // Assert
+ Assert.NotNull(context);
+ Assert.False(context.HasScheduledActivity(new WriteLine("NonExistent")));
+ }
+
+ [Theory(DisplayName = "Respects UseTokenFlow flag")]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task RespectsUseTokenFlowFlag(bool useTokenFlow)
+ {
+ // Arrange
+ var originalValue = Flowchart.UseTokenFlow;
+ Flowchart.UseTokenFlow = useTokenFlow;
+
+ try
+ {
+ var activity = new WriteLine("Test");
+ var flowchart = new Flowchart
+ {
+ Start = activity,
+ Activities = { activity }
+ };
+
+ // Act
+ var context = await ExecuteFlowchartAsync(flowchart);
+
+ // Assert - just verify it executes without error
+ Assert.NotNull(context);
+ }
+ finally
+ {
+ Flowchart.UseTokenFlow = originalValue;
+ }
+ }
+
+ [Fact(DisplayName = "Accepts empty connections collection")]
+ public async Task AcceptsEmptyConnections()
+ {
+ // Arrange
+ var activity = new WriteLine("Isolated");
+ var flowchart = new Flowchart
+ {
+ Start = activity,
+ Activities = { activity }
+ };
+
+ // Act
+ var context = await ExecuteFlowchartAsync(flowchart);
+
+ // Assert
+ Assert.True(context.HasScheduledActivity(activity));
+ }
+}
diff --git a/test/unit/Elsa.Activities.UnitTests/Http/SendHttpRequestTests.cs b/test/unit/Elsa.Activities.UnitTests/Http/SendHttpRequestTests.cs
index 62c9e4f21..461b29c83 100644
--- a/test/unit/Elsa.Activities.UnitTests/Http/SendHttpRequestTests.cs
+++ b/test/unit/Elsa.Activities.UnitTests/Http/SendHttpRequestTests.cs
@@ -285,4 +285,4 @@ public class SendHttpRequestTests
{
return (_, _) => throw ((TException)Activator.CreateInstance(typeof(TException), message)!);
}
-}
+}
\ No newline at end of file
diff --git a/test/unit/Elsa.Activities.UnitTests/Primitives/SetVariableOfTTests.cs b/test/unit/Elsa.Activities.UnitTests/Primitives/SetVariableOfTTests.cs
new file mode 100644
index 000000000..81e440bb3
--- /dev/null
+++ b/test/unit/Elsa.Activities.UnitTests/Primitives/SetVariableOfTTests.cs
@@ -0,0 +1,51 @@
+using Elsa.Testing.Shared;
+
+namespace Elsa.Activities.UnitTests.Primitives;
+
+public class SetVariableOfTTests
+{
+ [Fact]
+ public async Task Should_Set_Variable_Integer()
+ {
+ // Arrange
+ const int expected = 42; // The answer to life, the universe and everything.
+ var variable = new Variable("myVar", 0, "myVar");
+ var setVariable = new SetVariable(variable, new Input(expected));
+
+ // Act
+ var fixture = new ActivityTestFixture(setVariable);
+ var context = await fixture.ExecuteAsync();
+
+ // Assert
+ var result = variable.Get(context);
+ Assert.Equal(expected, result);
+ }
+
+ [Fact]
+ public async Task Should_Throw_When_Variable_Is_Null()
+ {
+ // Arrange
+ var setVariable = new SetVariable(null!, new Input("test value"));
+
+ // Act & Assert
+ var exception = await Record.ExceptionAsync(() => new ActivityTestFixture(setVariable).ExecuteAsync());
+
+ Assert.NotNull(exception);
+ }
+
+ [Fact]
+ public async Task Should_Set_Variable_To_Null_Value()
+ {
+ // Arrange
+ var variable = new Variable("myVar", "initial value", "myVar");
+ var setVariable = new SetVariable(variable, new Input((string?)null));
+
+ // Act
+ var fixture = new ActivityTestFixture(setVariable);
+ var context = await fixture.ExecuteAsync();
+
+ // Assert
+ var result = variable.Get(context);
+ Assert.Null(result);
+ }
+}
\ No newline at end of file
diff --git a/test/unit/Elsa.Activities.UnitTests/Primitives/SetVariableTests.cs b/test/unit/Elsa.Activities.UnitTests/Primitives/SetVariableTests.cs
index 361848f8e..b07b52aac 100644
--- a/test/unit/Elsa.Activities.UnitTests/Primitives/SetVariableTests.cs
+++ b/test/unit/Elsa.Activities.UnitTests/Primitives/SetVariableTests.cs
@@ -1,4 +1,6 @@
-using Elsa.Testing.Shared;
+using Elsa.Expressions.Models;
+using Elsa.Extensions;
+using Elsa.Testing.Shared;
using Elsa.Workflows;
namespace Elsa.Activities.UnitTests.Primitives;
@@ -6,12 +8,16 @@ namespace Elsa.Activities.UnitTests.Primitives;
public class SetVariableTests
{
[Fact]
- public async Task Should_Set_Variable_Integer()
+ public async Task Should_Set_Variable()
{
// Arrange
- const int expected = 42; // The answer to life, the universe and everything.
- var variable = new Variable("myVar", 0, "myVar");
- var setVariable = new SetVariable(variable, new Input(expected));
+ const int expected = 42;
+ var variable = new Variable("myVar", 0, "myVar");
+ var setVariable = new SetVariable
+ {
+ Variable = variable,
+ Value = new(expected)
+ };
// Act
var context = await ExecuteAsync(setVariable);
@@ -25,12 +31,17 @@ public class SetVariableTests
public async Task Should_Throw_When_Variable_Is_Null()
{
// Arrange
- var setVariable = new SetVariable(null!, new Input("test value"));
+ var setVariable = new SetVariable
+ {
+ Variable = null,
+ Value = new("test value")
+ };
// Act & Assert
var exception = await Record.ExceptionAsync(() => ExecuteAsync(setVariable));
Assert.NotNull(exception);
+ Assert.IsType(exception);
}
[Fact]
@@ -38,7 +49,11 @@ public class SetVariableTests
{
// Arrange
var variable = new Variable("myVar", "initial value", "myVar");
- var setVariable = new SetVariable(variable, new Input((string?)null));
+ var setVariable = new SetVariable
+ {
+ Variable = variable,
+ Value = new(new Literal(null))
+ };
// Act
var context = await ExecuteAsync(setVariable);
@@ -47,9 +62,9 @@ public class SetVariableTests
var result = variable.Get(context);
Assert.Null(result);
}
-
- private static Task ExecuteAsync(IActivity activity)
+
+ private static async Task ExecuteAsync(IActivity activity)
{
- return new ActivityTestFixture(activity).ExecuteAsync();
+ return await new ActivityTestFixture(activity).ExecuteAsync();
}
}
\ No newline at end of file