Add integration tests for RunJavaScript activity and introduce WorkflowTestFixture
- Implement tests to validate execution of valid and invalid scripts, outcome settings, workflow variable access, and complex script handling. - Add `WorkflowTestFixture` to streamline integration testing setup and execution.
This commit is contained in:
parent
5c655e0a44
commit
efe849d0bc
|
|
@ -0,0 +1,155 @@
|
|||
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;
|
||||
|
||||
/// <summary>
|
||||
/// A test fixture for integration testing workflows and activities.
|
||||
/// Provides a fluent API to configure services, run workflows, and capture output.
|
||||
/// </summary>
|
||||
public class WorkflowTestFixture
|
||||
{
|
||||
private readonly TestApplicationBuilder _testApplicationBuilder;
|
||||
private IServiceProvider? _services;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WorkflowTestFixture"/> class.
|
||||
/// </summary>
|
||||
/// <param name="testOutputHelper">The test output helper</param>
|
||||
public WorkflowTestFixture(ITestOutputHelper testOutputHelper)
|
||||
{
|
||||
_testApplicationBuilder = new TestApplicationBuilder(testOutputHelper);
|
||||
CapturingTextWriter = new CapturingTextWriter();
|
||||
_testApplicationBuilder.WithCapturingTextWriter(CapturingTextWriter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the capturing text writer that captures standard output from WriteLine activities.
|
||||
/// </summary>
|
||||
public CapturingTextWriter CapturingTextWriter { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the service provider. Throws if Build() hasn't been called yet.
|
||||
/// </summary>
|
||||
private IServiceProvider Services => _services ?? throw new InvalidOperationException("Build() must be called before accessing services");
|
||||
|
||||
/// <summary>
|
||||
/// Configures Elsa features.
|
||||
/// </summary>
|
||||
/// <param name="configure">Action to configure Elsa</param>
|
||||
/// <returns>The fixture instance for method chaining</returns>
|
||||
[UsedImplicitly]
|
||||
public WorkflowTestFixture ConfigureElsa(Action<IModule> configure)
|
||||
{
|
||||
_testApplicationBuilder.ConfigureElsa(configure);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the service collection.
|
||||
/// </summary>
|
||||
/// <param name="configure">Action to configure the service collection</param>
|
||||
/// <returns>The fixture instance for method chaining</returns>
|
||||
[UsedImplicitly]
|
||||
public WorkflowTestFixture ConfigureServices(Action<IServiceCollection> configure)
|
||||
{
|
||||
_testApplicationBuilder.ConfigureServices(configure);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a workflow to the service provider.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public WorkflowTestFixture AddWorkflow<T>() where T : IWorkflow
|
||||
{
|
||||
_testApplicationBuilder.AddWorkflow<T>();
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds activities from the assembly containing the specified type.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public WorkflowTestFixture AddActivitiesFrom<T>()
|
||||
{
|
||||
_testApplicationBuilder.AddActivitiesFrom<T>();
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds workflows from the specified relative directory.
|
||||
/// </summary>
|
||||
/// <param name="directory">The path segments of the directory</param>
|
||||
[UsedImplicitly]
|
||||
public WorkflowTestFixture WithWorkflowsFromDirectory(params string[] directory)
|
||||
{
|
||||
_testApplicationBuilder.WithWorkflowsFromDirectory(directory);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the service provider and populates registries.
|
||||
/// Must be called before running workflows.
|
||||
/// </summary>
|
||||
public async Task<WorkflowTestFixture> BuildAsync()
|
||||
{
|
||||
_services = _testApplicationBuilder.Build();
|
||||
await Services.PopulateRegistriesAsync();
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs a workflow and returns the workflow result.
|
||||
/// Automatically builds the fixture if not already built.
|
||||
/// </summary>
|
||||
/// <param name="workflow">The workflow to run</param>
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
/// <returns>The workflow result after execution</returns>
|
||||
public async Task<RunWorkflowResult> RunWorkflowAsync(IWorkflow workflow, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_services == null)
|
||||
await BuildAsync();
|
||||
|
||||
var workflowRunner = Services.GetRequiredService<IWorkflowRunner>();
|
||||
return await workflowRunner.RunAsync(workflow, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs an activity wrapped in a workflow and returns the workflow result.
|
||||
/// Automatically builds the fixture if not already built.
|
||||
/// </summary>
|
||||
/// <param name="activity">The activity to run</param>
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
/// <returns>The workflow result after execution</returns>
|
||||
public async Task<RunWorkflowResult> RunActivityAsync(IActivity activity, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_services == null)
|
||||
await BuildAsync();
|
||||
|
||||
var workflowRunner = Services.GetRequiredService<IWorkflowRunner>();
|
||||
return await workflowRunner.RunAsync(activity, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs a workflow by definition ID and returns the workflow state.
|
||||
/// Automatically builds the fixture if not already built.
|
||||
/// </summary>
|
||||
/// <param name="definitionId">The workflow definition ID</param>
|
||||
/// <param name="input">Optional input dictionary</param>
|
||||
/// <returns>The workflow state after execution</returns>
|
||||
public async Task<WorkflowState> RunWorkflowAsync(string definitionId, IDictionary<string, object>? input = null)
|
||||
{
|
||||
if (_services == null)
|
||||
await BuildAsync();
|
||||
|
||||
return await Services.RunWorkflowUntilEndAsync(definitionId, input);
|
||||
}
|
||||
}
|
||||
|
|
@ -25,7 +25,7 @@ public class RunJavaScript : CodeActivity<object?>
|
|||
/// <inheritdoc />
|
||||
public RunJavaScript(string script, [CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : this(source, line)
|
||||
{
|
||||
Script = new Input<string>(script);
|
||||
Script = new(script);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -2,14 +2,13 @@
|
|||
using System.Numerics;
|
||||
using System.Text.Json;
|
||||
using Elsa.Common.Converters;
|
||||
using Elsa.Expressions.Models;
|
||||
using Elsa.Expressions.JavaScript.Contracts;
|
||||
using Elsa.Expressions.Models;
|
||||
using Elsa.Testing.Shared;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Elsa.Expressions.JavaScript.IntegrationTests;
|
||||
namespace Elsa.JavaScript.IntegrationTests;
|
||||
public class JsonConverterTest(ITestOutputHelper testOutputHelper)
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider = new TestApplicationBuilder(testOutputHelper).Build();
|
||||
|
|
@ -23,7 +22,7 @@ public class JsonConverterTest(ITestOutputHelper testOutputHelper)
|
|||
}";
|
||||
var expressionExecutionContext = new ExpressionExecutionContext(_serviceProvider, new MemoryRegister());
|
||||
var result = (await javaScriptEvaluator.EvaluateAsync(script, typeof(ExpandoObject), expressionExecutionContext))!;
|
||||
var options = new JsonSerializerOptions()
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
Converters = { new BigIntegerJsonConverter() }
|
||||
};
|
||||
|
|
@ -47,9 +46,9 @@ public class JsonConverterTest(ITestOutputHelper testOutputHelper)
|
|||
}
|
||||
|
||||
[Fact(DisplayName = "BigIntegerJsonConverter Deserialize")]
|
||||
public async Task Test3()
|
||||
public Task Test3()
|
||||
{
|
||||
var options = new JsonSerializerOptions()
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
Converters = { new BigIntegerJsonConverter() }
|
||||
};
|
||||
|
|
@ -57,5 +56,6 @@ public class JsonConverterTest(ITestOutputHelper testOutputHelper)
|
|||
BigInteger bigInteger = JsonSerializer.Deserialize<BigInteger>("7239948466988781569", options);
|
||||
|
||||
Assert.Equal(7239948466988781569, bigInteger);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
using System.Text.Json;
|
||||
using Elsa.Expressions.Models;
|
||||
using Elsa.Expressions.JavaScript.Contracts;
|
||||
using Elsa.Expressions.Models;
|
||||
using Elsa.Testing.Shared;
|
||||
using Elsa.Workflows.Memory;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Elsa.Expressions.JavaScript.IntegrationTests;
|
||||
namespace Elsa.JavaScript.IntegrationTests;
|
||||
|
||||
public class JsonElementConverterTests(ITestOutputHelper testOutputHelper)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,101 @@
|
|||
using Elsa.Expressions.JavaScript.Activities;
|
||||
using Elsa.Testing.Shared;
|
||||
using Elsa.Workflows.Activities;
|
||||
using Elsa.Workflows.Memory;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Elsa.JavaScript.IntegrationTests;
|
||||
|
||||
public class RunJavaScriptTests(ITestOutputHelper testOutputHelper)
|
||||
{
|
||||
private readonly WorkflowTestFixture _fixture = new(testOutputHelper);
|
||||
|
||||
[Theory(DisplayName = "RunJavaScript should execute valid scripts successfully")]
|
||||
[InlineData("return 1 + 1;")]
|
||||
[InlineData("return 'Hello World';")]
|
||||
[InlineData("return 42;")]
|
||||
public async Task Should_Execute_Valid_Scripts(string script)
|
||||
{
|
||||
// Arrange
|
||||
var activity = new RunJavaScript { Script = new(script), Result = new() };
|
||||
|
||||
// Act
|
||||
await _fixture.RunActivityAsync(activity);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(_fixture.CapturingTextWriter.Lines);
|
||||
}
|
||||
|
||||
[Theory(DisplayName = "RunJavaScript should set outcomes correctly")]
|
||||
[InlineData("setOutcome('Success');")]
|
||||
[InlineData("setOutcomes(['Branch1', 'Branch2', 'Branch3']);")]
|
||||
public async Task Should_Set_Outcomes(string script)
|
||||
{
|
||||
// Arrange
|
||||
var activity = new RunJavaScript { Script = new(script) };
|
||||
|
||||
// Act
|
||||
await _fixture.RunActivityAsync(activity);
|
||||
|
||||
// Assert
|
||||
Assert.Null(_fixture.CapturingTextWriter.Lines.FirstOrDefault());
|
||||
}
|
||||
|
||||
[Theory(DisplayName = "RunJavaScript should not execute invalid scripts")]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
public async Task Should_Not_Execute_Invalid_Scripts(string script)
|
||||
{
|
||||
// Arrange
|
||||
var activity = new RunJavaScript { Script = new(script), Result = new() };
|
||||
|
||||
// Act
|
||||
await _fixture.RunActivityAsync(activity);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(_fixture.CapturingTextWriter.Lines);
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "RunJavaScript should access workflow variables")]
|
||||
public async Task Should_Access_Workflow_Variables()
|
||||
{
|
||||
// Arrange
|
||||
var myVar = new Variable<int>("MyVar", 100);
|
||||
var script = "return getMyVar();";
|
||||
var workflow = new Workflow
|
||||
{
|
||||
Root = new RunJavaScript { Script = new(script), Result = new() },
|
||||
Variables = { myVar }
|
||||
};
|
||||
|
||||
// Act
|
||||
await _fixture.RunActivityAsync(workflow);
|
||||
|
||||
// Assert - workflow completes successfully
|
||||
Assert.Empty(_fixture.CapturingTextWriter.Lines);
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "RunJavaScript should execute complex script with multiple statements and outcomes")]
|
||||
public async Task Should_Execute_Complex_Script_With_Multiple_Statements()
|
||||
{
|
||||
// Arrange
|
||||
var script = @"
|
||||
var x = 10;
|
||||
var y = 20;
|
||||
var sum = x + y;
|
||||
if (sum > 25) {
|
||||
setOutcome('Large');
|
||||
} else {
|
||||
setOutcome('Small');
|
||||
}
|
||||
return sum;
|
||||
";
|
||||
var activity = new RunJavaScript { Script = new(script), Result = new() };
|
||||
|
||||
// Act
|
||||
await _fixture.RunActivityAsync(activity);
|
||||
|
||||
// Assert - workflow completes successfully
|
||||
Assert.Empty(_fixture.CapturingTextWriter.Lines);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,12 @@
|
|||
using System.Text.Json;
|
||||
using Elsa.Expressions.Models;
|
||||
using Elsa.Expressions.JavaScript.Contracts;
|
||||
using Elsa.Expressions.Models;
|
||||
using Elsa.Testing.Shared;
|
||||
using Elsa.Workflows.Memory;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Elsa.Expressions.JavaScript.IntegrationTests;
|
||||
namespace Elsa.JavaScript.IntegrationTests;
|
||||
|
||||
public class ToJsonTests(ITestOutputHelper testOutputHelper)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ public class ForkTests
|
|||
private readonly IWorkflowRunner _workflowRunner;
|
||||
private readonly CapturingTextWriter _capturingTextWriter = new();
|
||||
private readonly IWorkflowBuilderFactory _workflowBuilderFactory;
|
||||
private IServiceProvider _services;
|
||||
private readonly IServiceProvider _services;
|
||||
|
||||
public ForkTests(ITestOutputHelper testOutputHelper)
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue