elsa-core/doc/qa/test-guidelines.md
Sipke Schoorstra 762f23dbc3
Adds SetVariable activity unit tests (#6989)
* Update doc/qa/test-guidelines.md

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>

* Improvements on maintainability of sendhttprequest unit tests

* Improving tests and scheduled activity evaluation for activity context

* Refactor and splitting unnecessary grouped tests

* Improvements on SendHttp Unit tests

* Improvements on tests

* Update test/unit/Elsa.Activities.UnitTests/HTTP/SendHttpRequestTests.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Small suggestions from copilot

* Update test/unit/Elsa.Activities.UnitTests/HTTP/SendHttpRequestTests.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* small copilot suggestion

* Introduce Scheduler Strategy Interfaces and Implementations for Workflow and Activity Execution Contexts

- Added `IWorkflowExecutionContextSchedulerStrategy` and `IActivityExecutionContextSchedulerStrategy` interfaces.
- Implemented `WorkflowExecutionContextSchedulerStrategy` and `ActivityExecutionContextSchedulerStrategy` for scheduling activities in workflows.
- Refactored scheduling logic to utilize the new scheduler strategies.
- Updated unit tests and test helpers to reflect refactoring, introducing fake implementations for testing purposes.
- Adjusted background execution scheduling and improved extensibility for custom scheduler strategies.

* Refactor `SendHttpRequestTests`: simplify scheduling assertions, use shared extensions, and standardize method naming. Streamline helper methods and remove unused test logic.

* Refactor: Replace `ActivityTestHelper` with `ActivityTestFixture` in unit tests for streamlined activity testing

- Introduced `ActivityTestFixture` with a fluent API for better test setup and execution of activities.
- Added extension methods `ActivityTestFixtureExtensions` and `ActivityTestFixtureHttpExtensions` for configuring attributes and HTTP services.
- Updated test guidelines and unit tests to use the new fixture and extensions.
- Removed `ActivityTestHelper`.

* Refactor: Move `ActivityTestFixture` and related extensions to shared project for reuse across test suites

- Consolidated `ActivityTestFixture`, `ActivityTestFixtureExtensions`, and `ActivityTestFixtureHttpExtensions` into `Elsa.Testing.Shared`.
- Updated namespaces and imports across unit tests to reflect new structure.
- Enhanced `AssertActivityAttributes` and added fluent configuration APIs.
- Adjusted `Directory.Packages.props` with new dependencies, including `NSubstitute` and `xunit.assert`.

* Refactor `SetVariableTests`: inline `ActivityTestFixture` initialization to simplify test setup.

* Refactor `ActivityTestFixture`: eliminate redundant field `_services`, add `UsedImplicitly` attributes, and improve service collection management

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Add XML documentation for `ActivityExecutionContextExtensions`, detailing methods and parameters.

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Add XML documentation for scheduler strategies and their methods

- Updated `IActivityExecutionContextSchedulerStrategy` and `IWorkflowExecutionContextSchedulerStrategy` interfaces with XML summaries for methods.
- Added XML documentation to implementations (`ActivityExecutionContextSchedulerStrategy`, `WorkflowExecutionContextSchedulerStrategy`) and test fakes for clarity.

* Refactor `WriteLineTests`: consolidate duplicate test logic, simplify setup with shared helper method, and enhance readability in assertions.

* Refactor `WriteLineTests` and `SetVariableTests`: replace `WriteLineAsync` assertions with synchronous `WriteLine`, streamline exception recording in `SetVariableTests`, and remove unused imports.

* Add integration and unit tests for `SetVariable` activity: ensure variable scoping, null handling, and value setting are covered.

* Add new `SetVariableOfTTests` and update `SetVariableTests` for improved test coverage and type handling.

* Add new unit and integration tests for activity input and expression evaluation

- Introduced comprehensive test suites covering activity input evaluation, expression handling, and fault scenarios.
- Added unit tests for `ExpressionDescriptorRegistry`, `ExpressionEvaluator`, and activity execution context extensions.
- Added integration tests: `CustomInputEvaluatorTests`, `InputEvaluationErrorTests`, and `InputPropertyEvaluationTests`.
- Enhanced code coverage for edge cases and async evaluation logic.

* Remove unused `RunWorkflowAsync` extension method from `RunActivityExtensions`.

* Rename `CustomInputEvaluatorTests` to `InputEvaluationTests` for consistency with naming conventions.

* Remove unused `Elsa.Workflows.Activities` import from `RunActivityExtensions`.

* Remove redundant comments from `InputEvaluationErrorTests` for clarity.

* Refactor unit tests to streamline activity and expression evaluations

- Refactored `ExecuteActivityAsync` and `ExecuteWriteLineAsync` into shared helpers for consistency and reuse across tests.
- Replaced redundant mock setups with helper methods in `ExpressionDescriptorRegistryTests`.
- Simplified test setup for expression and activity evaluation by removing unused imports and consolidating configuration logic.
- Enhanced readability by reducing duplicate code and leveraging shared utility methods.

* Refactor activity input evaluation tests

- Extracted `CreateContextAsync` helper into `EvaluationTestHelpers` for reuse across evaluation test suites.
- Replaced inline activity context setup with shared helper in `InputPropertyEvaluationTests`, `WrappedInputEvaluationTests`, and related test suites.
- Simplified test method names for clarity and consistency.
- Updated test annotations to enhance readability and align with naming conventions.

* Remove redundant test cases and unused imports

- Deleted duplicated and non-essential test cases across evaluation test suites.
- Removed unused imports to improve code cleanliness and readability.
- Streamlined variable initializations and method calls within test setups.

* Remove redundant test case from `InputEvaluationErrorTests`

- Deleted the `ContinuesEvaluationForMultipleInputs` test, as it overlaps with existing tests and does not provide additional coverage.

* Remove redundant assertion from `InputPropertyEvaluationTests`

- Deleted `Assert.True(context.GetHasEvaluatedProperties())`, as it is unnecessary for verifying test outcomes.

* Remove redundant test cases from `WrappedInputEvaluationTests`

- Deleted `UsesDefaultValueWhenInputIsNull` and `EvaluatesExpression` tests as they are either duplicated or unnecessary for current test coverage.

* Add unit test projects for `Elsa.Workflows.Management` and `Elsa.Expressions`

- Introduced new test projects to separate and organize unit tests for `Elsa.Workflows.Management` and `Elsa.Expressions`.
- Updated `Elsa.sln` to include references to the newly added test projects.
- Adjusted namespaces in affected test classes for consistency with the updated project structure.

* Refactor `ExpressionEvaluatorTests` for clarity and consistency

- Simplified test method names and annotations for improved readability.
- Replaced duplicate mock setups with helper functions (`CreateContextAsync`, `CreateContextWithMockHandlerAsync`, and related methods).
- Streamlined test setups by removing redundant code and consolidating context creation logic.
- Updated test annotations to include descriptive `DisplayName` attributes.

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Remove redundant comment from `ExpressionEvaluatorTests` for clarity

* Remove redundant blank lines from unit test classes

- Eliminated unnecessary blank lines across `ActivityExecutionContextExtensions` test suites to improve code readability and consistency.

* Add unit tests for Flowchart activity

- Introduced `FlowchartTests` to verify common Flowchart behavior, including start activity scheduling, execution without a start activity, and `UseTokenFlow` handling.
- Added `FlowchartTestHelpers` to encapsulate shared test logic.
- Updated project file to include the new `Flow` folder for organization.

* Add integration tests for Flowchart activity execution strategies

- Introduced `FlowchartCounterBasedTests` and `FlowchartTokenBasedTests` to verify different Flowchart execution strategies.
- Added `FlowchartTestHelpers` for shared test logic, including helper methods for creating various flowchart configurations and connections.
- Enhanced test coverage with scenarios for linear, parallel, and conditional flows, mixed merge modes, nested flowcharts, and token handling.
- Updated project structure to include new test classes under the `Flow` folder.

* Remove obsolete tag from `FlowJoin` activity description

* Handle both string and enum values in `GetMergeMode` for backwards compatibility

* Group flowchart integration tests into non-parallelizable test collection

- Introduced `FlowchartTestCollection` to prevent parallel execution of flowchart tests due to shared `Flowchart.UseTokenFlow` flag.
- Updated `FlowchartCounterBasedTests` and `FlowchartTokenBasedTests` to implement `IDisposable` and manage `UseTokenFlow` cleanup.

* Remove unused `Flow` folder reference from test project file

* Add `ParallelTests` for unit and integration testing with various scenarios (#6988)

- Added unit tests for `Parallel` activity to ensure proper scheduling of child activities, including empty and mixed activity cases.
- Added integration tests to validate execution flow and edge cases for `Parallel` activities (e.g., nested parallelism, fault handling).
- Enhanced `ScheduleChildrenAsync` in `Parallel` to handle no activity scenario by completing immediately.

* Fix null-check and memory declaration in `ActivityTestFixture` to prevent potential `NullReferenceException`.

* Introduce Scheduler Strategy Interfaces and Implementations for Workflow and Activity Execution Contexts (#6984)

* Introduce Scheduler Strategy Interfaces and Implementations for Workflow and Activity Execution Contexts

- Added `IWorkflowExecutionContextSchedulerStrategy` and `IActivityExecutionContextSchedulerStrategy` interfaces.
- Implemented `WorkflowExecutionContextSchedulerStrategy` and `ActivityExecutionContextSchedulerStrategy` for scheduling activities in workflows.
- Refactored scheduling logic to utilize the new scheduler strategies.
- Updated unit tests and test helpers to reflect refactoring, introducing fake implementations for testing purposes.
- Adjusted background execution scheduling and improved extensibility for custom scheduler strategies.

* Refactor `SendHttpRequestTests`: simplify scheduling assertions, use shared extensions, and standardize method naming. Streamline helper methods and remove unused test logic.

* Refactor: Replace `ActivityTestHelper` with `ActivityTestFixture` in unit tests for streamlined activity testing

- Introduced `ActivityTestFixture` with a fluent API for better test setup and execution of activities.
- Added extension methods `ActivityTestFixtureExtensions` and `ActivityTestFixtureHttpExtensions` for configuring attributes and HTTP services.
- Updated test guidelines and unit tests to use the new fixture and extensions.
- Removed `ActivityTestHelper`.

* Refactor: Move `ActivityTestFixture` and related extensions to shared project for reuse across test suites

- Consolidated `ActivityTestFixture`, `ActivityTestFixtureExtensions`, and `ActivityTestFixtureHttpExtensions` into `Elsa.Testing.Shared`.
- Updated namespaces and imports across unit tests to reflect new structure.
- Enhanced `AssertActivityAttributes` and added fluent configuration APIs.
- Adjusted `Directory.Packages.props` with new dependencies, including `NSubstitute` and `xunit.assert`.

* Refactor `SetVariableTests`: inline `ActivityTestFixture` initialization to simplify test setup.

* Refactor `ActivityTestFixture`: eliminate redundant field `_services`, add `UsedImplicitly` attributes, and improve service collection management

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Add XML documentation for `ActivityExecutionContextExtensions`, detailing methods and parameters.

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Add XML documentation for scheduler strategies and their methods

- Updated `IActivityExecutionContextSchedulerStrategy` and `IWorkflowExecutionContextSchedulerStrategy` interfaces with XML summaries for methods.
- Added XML documentation to implementations (`ActivityExecutionContextSchedulerStrategy`, `WorkflowExecutionContextSchedulerStrategy`) and test fakes for clarity.

* Refactor `WriteLineTests`: consolidate duplicate test logic, simplify setup with shared helper method, and enhance readability in assertions.

* Refactor `WriteLineTests` and `SetVariableTests`: replace `WriteLineAsync` assertions with synchronous `WriteLine`, streamline exception recording in `SetVariableTests`, and remove unused imports.

* Add `ParallelTests` for unit and integration testing with various scenarios (#6988)

- Added unit tests for `Parallel` activity to ensure proper scheduling of child activities, including empty and mixed activity cases.
- Added integration tests to validate execution flow and edge cases for `Parallel` activities (e.g., nested parallelism, fault handling).
- Enhanced `ScheduleChildrenAsync` in `Parallel` to handle no activity scenario by completing immediately.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Add new unit and integration tests for activity input and expression evaluation (#6990)

* Introduce Scheduler Strategy Interfaces and Implementations for Workflow and Activity Execution Contexts

- Added `IWorkflowExecutionContextSchedulerStrategy` and `IActivityExecutionContextSchedulerStrategy` interfaces.
- Implemented `WorkflowExecutionContextSchedulerStrategy` and `ActivityExecutionContextSchedulerStrategy` for scheduling activities in workflows.
- Refactored scheduling logic to utilize the new scheduler strategies.
- Updated unit tests and test helpers to reflect refactoring, introducing fake implementations for testing purposes.
- Adjusted background execution scheduling and improved extensibility for custom scheduler strategies.

* Refactor `SendHttpRequestTests`: simplify scheduling assertions, use shared extensions, and standardize method naming. Streamline helper methods and remove unused test logic.

* Refactor: Replace `ActivityTestHelper` with `ActivityTestFixture` in unit tests for streamlined activity testing

- Introduced `ActivityTestFixture` with a fluent API for better test setup and execution of activities.
- Added extension methods `ActivityTestFixtureExtensions` and `ActivityTestFixtureHttpExtensions` for configuring attributes and HTTP services.
- Updated test guidelines and unit tests to use the new fixture and extensions.
- Removed `ActivityTestHelper`.

* Refactor: Move `ActivityTestFixture` and related extensions to shared project for reuse across test suites

- Consolidated `ActivityTestFixture`, `ActivityTestFixtureExtensions`, and `ActivityTestFixtureHttpExtensions` into `Elsa.Testing.Shared`.
- Updated namespaces and imports across unit tests to reflect new structure.
- Enhanced `AssertActivityAttributes` and added fluent configuration APIs.
- Adjusted `Directory.Packages.props` with new dependencies, including `NSubstitute` and `xunit.assert`.

* Refactor `SetVariableTests`: inline `ActivityTestFixture` initialization to simplify test setup.

* Refactor `ActivityTestFixture`: eliminate redundant field `_services`, add `UsedImplicitly` attributes, and improve service collection management

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Add XML documentation for `ActivityExecutionContextExtensions`, detailing methods and parameters.

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Add XML documentation for scheduler strategies and their methods

- Updated `IActivityExecutionContextSchedulerStrategy` and `IWorkflowExecutionContextSchedulerStrategy` interfaces with XML summaries for methods.
- Added XML documentation to implementations (`ActivityExecutionContextSchedulerStrategy`, `WorkflowExecutionContextSchedulerStrategy`) and test fakes for clarity.

* Refactor `WriteLineTests`: consolidate duplicate test logic, simplify setup with shared helper method, and enhance readability in assertions.

* Refactor `WriteLineTests` and `SetVariableTests`: replace `WriteLineAsync` assertions with synchronous `WriteLine`, streamline exception recording in `SetVariableTests`, and remove unused imports.

* Add new unit and integration tests for activity input and expression evaluation

- Introduced comprehensive test suites covering activity input evaluation, expression handling, and fault scenarios.
- Added unit tests for `ExpressionDescriptorRegistry`, `ExpressionEvaluator`, and activity execution context extensions.
- Added integration tests: `CustomInputEvaluatorTests`, `InputEvaluationErrorTests`, and `InputPropertyEvaluationTests`.
- Enhanced code coverage for edge cases and async evaluation logic.

* Remove unused `RunWorkflowAsync` extension method from `RunActivityExtensions`.

* Rename `CustomInputEvaluatorTests` to `InputEvaluationTests` for consistency with naming conventions.

* Remove unused `Elsa.Workflows.Activities` import from `RunActivityExtensions`.

* Remove redundant comments from `InputEvaluationErrorTests` for clarity.

* Refactor unit tests to streamline activity and expression evaluations

- Refactored `ExecuteActivityAsync` and `ExecuteWriteLineAsync` into shared helpers for consistency and reuse across tests.
- Replaced redundant mock setups with helper methods in `ExpressionDescriptorRegistryTests`.
- Simplified test setup for expression and activity evaluation by removing unused imports and consolidating configuration logic.
- Enhanced readability by reducing duplicate code and leveraging shared utility methods.

* Refactor activity input evaluation tests

- Extracted `CreateContextAsync` helper into `EvaluationTestHelpers` for reuse across evaluation test suites.
- Replaced inline activity context setup with shared helper in `InputPropertyEvaluationTests`, `WrappedInputEvaluationTests`, and related test suites.
- Simplified test method names for clarity and consistency.
- Updated test annotations to enhance readability and align with naming conventions.

* Remove redundant test cases and unused imports

- Deleted duplicated and non-essential test cases across evaluation test suites.
- Removed unused imports to improve code cleanliness and readability.
- Streamlined variable initializations and method calls within test setups.

* Remove redundant test case from `InputEvaluationErrorTests`

- Deleted the `ContinuesEvaluationForMultipleInputs` test, as it overlaps with existing tests and does not provide additional coverage.

* Remove redundant assertion from `InputPropertyEvaluationTests`

- Deleted `Assert.True(context.GetHasEvaluatedProperties())`, as it is unnecessary for verifying test outcomes.

* Remove redundant test cases from `WrappedInputEvaluationTests`

- Deleted `UsesDefaultValueWhenInputIsNull` and `EvaluatesExpression` tests as they are either duplicated or unnecessary for current test coverage.

* Add unit test projects for `Elsa.Workflows.Management` and `Elsa.Expressions`

- Introduced new test projects to separate and organize unit tests for `Elsa.Workflows.Management` and `Elsa.Expressions`.
- Updated `Elsa.sln` to include references to the newly added test projects.
- Adjusted namespaces in affected test classes for consistency with the updated project structure.

* Refactor `ExpressionEvaluatorTests` for clarity and consistency

- Simplified test method names and annotations for improved readability.
- Replaced duplicate mock setups with helper functions (`CreateContextAsync`, `CreateContextWithMockHandlerAsync`, and related methods).
- Streamlined test setups by removing redundant code and consolidating context creation logic.
- Updated test annotations to include descriptive `DisplayName` attributes.

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Remove redundant comment from `ExpressionEvaluatorTests` for clarity

* Remove redundant blank lines from unit test classes

- Eliminated unnecessary blank lines across `ActivityExecutionContextExtensions` test suites to improve code readability and consistency.

* Update src/modules/Elsa.Expressions/Services/ExpressionEvaluator.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Expand `test-guidelines.md` with testing best practices, helper references, and example snippets

- Added detailed guidance on test project organization, updated helper documentation, and streamlined example code for activity unit testing.
- Introduced scheduler strategy information and integration test patterns for deterministic tests.
- Clarified usage of shared infrastructure like `ActivityTestFixture` and `AsyncWorkflowRunner`.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Fix merge issue

* Add missing `using Elsa.Expressions.Models` directive to `SetVariableTests`

* Update `SetVariableTests` and `ActivityTestFixture` to fix exception type assertion and improve test utility execution handling.

* Update `SetVariableTests` and `ActivityTestFixture` to handle null variables, fix exception type assertion, and simplify context usage.

---------

Co-authored-by: lucas.hipolito <lukhipolito@yahoo.com.br>
Co-authored-by: lukhipolito-nexxbiz <lucas.hipolito@nexxbiz.io>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-22 20:54:58 +02:00

24 KiB
Raw Blame History

Elsa Core — Testing Strategy

Purpose

This document is a practical test guideline. It tells you what to test, when to test it, and how to write deterministic, actionable tests using the repository's existing test helpers and patterns.


Summary

The philosophy of testing in Elsa can be summarized as:

Whenever a test fails, it should provide a clear direction towards the cause of the problem.

Tests should be fast, deterministic, and precise: they should pinpoint the failing subsystem (activity, invoker, persistence, scheduler, etc.) with minimal noise.

For contributors, tests are the first line of code review: they must document intended behaviour and prevent regressions.


Glossary

  • Activity — a single unit of workflow logic (e.g., WriteLine, If, ForEach, HttpRequest).
  • Workflow — a graph of activities connected by control flow.
  • Workflow Definition — a serializable representation of a workflow (JSON or code).
  • Workflow Instance — a persisted execution of a workflow definition, including state, variables, and journal.
  • Bookmark — a pause point in a workflow where execution is suspended until an external event resumes it.
  • Invoker — the core engine component that orchestrates workflow execution, including activity execution, scheduling, and state transitions.
  • Scheduler — the subsystem that manages background tasks, timers, and resumption of workflows.
  • Journal — a log of all activity executions and state changes in a workflow instance.
  • Persistence — the storage mechanism for workflow definitions and instances (e.g., EF Core, MongoDB).
  • Unit Test — a test that verifies a small, isolated piece of code (such as a function or method) works as expected.
  • Integration Test — a test that verifies the interaction between multiple components or subsystems works as expected.
  • Component Test — a test that verifies the behavior of a larger part of the system, often involving persistence and external dependencies.

High-level testing pyramid

  • Unit tests — single-class logic (activities, converters, expression evaluators, serializers, service providers). Fast; no persistence.
  • Integration tests — multiple Elsa subsystems together (e.g., invoker + activities + registries). In-process; may deserialize workflow JSON. Use IWorkflowRunner.RunAsync and PopulateRegistriesAsync() when using existing definitions.
  • Component tests — persisted behaviour, journal/instance store assertions, bookmarks/resumption across lifecycle boundaries. Use AppComponentTest to instantiate and IWorkflowInstanceStore queries for assertions.

Each test layer has distinct goals and clear boundaries — see Which parts of Elsa to test for precise mapping of which aspects belong to which layer.


Quick Start for Contributors

Before you write a test:

  1. ✅ Understand what you're testing (see Which parts of Elsa to test)
  2. ✅ Choose the right test layer (unit vs integration vs component)
  3. ✅ Use existing helpers (don't reinvent - see Test Helpers Reference)

5-Minute Checklist:

  • Read the relevant section below for your change type:
  • Follow steps and code patterns in that section
  • Run tests locally: dotnet test
  • Verify no flaky behavior (run 10 times: dotnet test --no-build -- repeat 10)

Characteristics for testing

  • Activities: First-class pluggable units. Each activity implements execution logic and interacts with the ActivityExecutionContext. More details in Activities.

  • Workflows: Graphs of activities. A workflow can run synchronously or schedule asynchronous work (bookmarks, timers). When you run a workflow in-process with IWorkflowRunner.RunAsync, the runner will return when synchronous work completes. Some activities set RunAsynchronously causing background scheduling — tests need to take care when asserting. More details in Workflow execution.


Test Project Organization

Tests are organized by module and test type:

  • test/unit/Elsa.Activities.UnitTests - Activity unit tests
  • test/unit/Elsa.Expressions.UnitTests - Expression evaluation unit tests
  • test/unit/Elsa.Workflows.Core.UnitTests - Core workflow unit tests (ActivityExecutionContext extensions, etc.)
  • test/unit/Elsa.Workflows.Management.UnitTests - Workflow management unit tests
  • test/integration/Elsa.Workflows.IntegrationTests - Workflow integration tests
  • test/component/Elsa.Workflows.ComponentTests - End-to-end component tests

Shared test infrastructure:

  • src/common/Elsa.Testing.Shared - Shared test helpers (ActivityTestFixture, fake strategies, extensions)
  • src/common/Elsa.Testing.Shared.Integration - Integration test helpers (RunActivityExtensions, PopulateRegistriesAsync)
  • src/common/Elsa.Testing.Shared.Component - Component test helpers (event handlers, workflow events)

Which parts of Elsa to test, and which test types to use

This section maps Elsa aspects to the exact kinds of tests you should write, with examples and code patterns referencing repository conventions.

Activities

Unit tests:

  • Test the activity class logic only (no persistence, no scheduler). Cover configuration permutations and boundary inputs.
  • Use ActivityTestFixture to configure and execute the activity and obtain an ActivityExecutionContext for assertions.
  • Use fluent methods: ConfigureServices() to add custom services, ConfigureContext() to set up context state
  • Assert using context.GetActivityOutput()(extension method) for outputs, or context.Get() for variables.

Example (does not set output):

[Fact]
public async Task Should_Set_Variable_Integer()
{
    // Arrange
    const int expected = 42;
    var variable = new Variable<int>("myVar", 0, "myVar");
    var setVariable = new SetVariable<int>(variable, new Input<int>(expected));

    // Act
    var fixture = new ActivityTestFixture(setVariable);
    var context = await fixture.ExecuteAsync();

    // Assert
    var result = context.Get(variable);
    Assert.Equal(expected, result);
}

Example (sets output using fluent configuration):

[Fact]
public async Task Should_Send_Get_Request_And_Handle_Success_Response()
{
    // Arrange
    var expectedUrl = new Uri("https://example.com");
    var sendHttpRequest = new SendHttpRequest
    {
        Url = new Input<Uri?>(expectedUrl),
        Method = new Input<string>("GET")
    };

    // Act
    var fixture = new ActivityTestFixture(sendHttpRequest)
        .WithHttpServices(responseHandler); // HTTP-specific extension
    var context = await fixture.ExecuteAsync();

    // Assert
    var statusCodeOutput = context.GetActivityOutput(_ => sendHttpRequest.StatusCode);
    Assert.Equal(200, statusCodeOutput);
}

Example (checking scheduled activities):

[Fact]
public async Task Should_Schedule_Child_Activity()
{
    // Arrange
    var childActivity = new WriteLine("Hello");
    var parentActivity = new Sequence { Activities = [childActivity] };

    // Act
    var fixture = new ActivityTestFixture(parentActivity);
    var context = await fixture.ExecuteAsync();

    // Assert
    Assert.True(context.HasScheduledActivity(childActivity));
}

Example (checking activity outcomes):

[Fact]
public async Task Should_Return_Multiple_Outcomes()
{
    // Arrange
    var flowFork = new FlowFork
    {
        Branches = new(new[] { "Branch1", "Branch2", "Branch3" })
    };

    // Act
    var context = await new ActivityTestFixture(flowFork).ExecuteAsync();

    // Assert - Check all outcomes
    var outcomes = context.GetOutcomes().ToList();
    Assert.Equal(3, outcomes.Count);
    Assert.Contains("Branch1", outcomes);
    Assert.Contains("Branch2", outcomes);
    Assert.Contains("Branch3", outcomes);
}

[Fact]
public async Task Should_Return_Default_Outcome()
{
    // Arrange
    var flowSwitch = new FlowSwitch();

    // Act
    var context = await new ActivityTestFixture(flowSwitch).ExecuteAsync();

    // Assert - Check single outcome
    Assert.True(context.HasOutcome("Default"));
}

Integration tests:

  • Place the activity inside a minimal workflow definition and run via IWorkflowRunner.RunAsync. 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.

Pattern note: RunAsync returns a RunWorkflowResult (or equivalent) containing the WorkflowInstance and output variables when run to completion. Use returned state for deterministic assertions where possible.


Workflow execution (invoker, middleware, bookmarks)

Unit tests:

  • Rare: low-level pure helpers in the invoker may have unit tests for edge cases. Most invoker behavior requires integration testing.

Integration tests:

  • Use IWorkflowRunner.RunAsync with small workflows to test variables propagation, branch logic (If/ForEach/Parallel), expression evaluation, and RunAsynchronously flags.
  • When a workflow schedules async child work (bookmarks), simulate resumption by calling resume APIs.

Call the workflow runner to execute a workflow object or a loaded definition. Prefer this when asserting logical flow and outputs.

var runner = serviceProvider.GetRequiredService<IWorkflowRunner>();
await serviceProvider.PopulateRegistriesAsync();
var runResult = await runner.RunAsync(workflow);
Assert.Equal(WorkflowStatus.Finished, runResult.WorkflowInstance!.Status);

Component tests (persistence & resumption):

  • Start a workflow that creates a bookmark. Persisted instance must be found via IWorkflowInstanceStore after the creation point. Simulate host restart by disposing and rebuilding the service provider (keeping the same persistence store) and resume the bookmark to assert resumption completes.

Code pattern to resume a bookmark (integration/component):

// assume instanceId found via RunAsync or correlation id
await workflowTriggerService.ResumeAsync(instanceId, activityId, input, CancellationToken.None);
var resumed = await runner.RunAsync(workflowInstance);
Assert.Equal(WorkflowStatus.Finished, resumed.WorkflowInstance.Status);

Test Helpers Reference (Quick Lookup)

Helper Purpose Use When
TestApplicationBuilder Build test service provider All tests as entry point,
except activities unit tests (see below)
ActivityTestFixture Configure and run single activity in isolation Unit testing activities
ActivityTestFixture.ConfigureServices Fluent API to add services to fixture Activity unit tests requiring custom services
ActivityTestFixture.ConfigureContext Fluent API to configure execution context before execution Setting up test-specific context state
ActivityTestFixture.BuildAsync Build context without executing activity When you need context setup without execution
ActivityTestFixture.ExecuteAsync Execute activity and return context Standard activity unit test execution
context.GetActivityOutput Get output from activity using expression selector Asserting activity outputs in unit tests
context.HasScheduledActivity Check if activity is scheduled Verifying scheduling behavior in unit tests
context.GetOutcomes Get all outcomes from activity execution Asserting multiple outcomes in unit tests
context.HasOutcome Check if activity has specific outcome Asserting single outcome in unit tests
IWorkflowRunner.RunAsync Execute workflow in-process Integration / Component tests
RunActivityExtensions.RunActivityAsync Run single activity as workflow Integration tests for single activities
PopulateRegistriesAsync Register types for JSON deserialization Loading JSON workflows.
Integration tests only
IWorkflowInstanceStore Query persisted instances Component tests (persistence)
AsyncWorkflowRunner.RunAndAwaitWorkflowCompletionAsync Drive workflow to completion with activity tracking Complex async scenarios.
Component tests only
FakeActivityExecutionContextSchedulerStrategy Fake scheduler for unit tests Automatically configured in ActivityTestFixture
FakeWorkflowExecutionContextSchedulerStrategy Fake workflow scheduler for unit tests Automatically configured in ActivityTestFixture

Scheduler Strategies for Testing

When testing activities in isolation, Elsa provides fake scheduler strategy implementations that bypass the production scheduling logic:

Available Strategies

  • FakeActivityExecutionContextSchedulerStrategy (source) - Schedules activities immediately within the activity execution context for deterministic testing
  • FakeWorkflowExecutionContextSchedulerStrategy (source) - Schedules activities immediately at the workflow level for deterministic testing

These strategies are automatically configured when using ActivityTestFixture and ensure that scheduled activities execute synchronously in tests, making assertions deterministic.

Note: You do not need to manually configure these strategies - ActivityTestFixture handles this for you.


Decision helper (what to add — follow in order)

  1. Changed code is a single activity class with no persistence/external calls? → Unit test only.
  2. Change touches multiple activities or workflow logic (If, ForEach, Parallel, Flow activities, etc.)? → Integration test using IWorkflowRunner.RunAsync and a small workflow.
  3. Change touches invoker/scheduler/bookmarks or similar multi-component feature? → Integration test using IWorkflowRunner.RunAsync and a small workflow. If persistence semantics change, add component tests.
  4. Change touches persistence/serializers or requires durable evidence (journal, bookmarks)? → Component tests against IWorkflowInstanceStore.

Rule of thumb:

  • If it’s about internal logic, write a unit test.
  • If it’s about collaboration between components, write an integration test.
  • If it’s about end-to-end feature behavior, write a component test

When in doubt, add the minimal unit tests plus one integration test that reproduces the scenario.


Deterministic patterns to avoid flaky tests

  1. For activity unit tests, prefer returned state from ActivityTestFixture.ExecuteAsync. Always inspect on the returned context — it is deterministic for synchronous workflows.
  2. Resume bookmarks explicitly. Do not wait for external schedulers — call the engine's resume/trigger APIs in your test to continue execution.
  3. For integration tests, locate instances deterministically. Use an instance id returned by RunAsync or attach a CorrelationId test variable and query IWorkflowInstanceStore.FindByCorrelationIdAsync(...). Avoid using "latest" queries.

Failure testing (faults & incidents)

  • Integration test faulted workflows: build a workflow that throws and run via RunAsync — assert WorkflowInstance.Status == Faulted on the returned state or via IWorkflowInstanceStore.
  • Component tests for recovery/resume: persist a faulted instance (or cause a host restart scenario), run your recovery logic, and assert the final state.

Practical test recipes & snippets

Unit test (activity) — pattern

[Fact]
public async Task MyActivity_Test()
{
    var activity = new ActivityToTest();

    // Act
    var fixture = new ActivityTestFixture(activity);
    var context = await fixture.ExecuteAsync();

    // assert behavior of activity in isolation
}

Integration test — pattern using IWorkflowRunner.RunAsync

[Fact]
public async Task Workflow_With_MyActivity_Completes()
{
    var sp = new TestApplicationBuilder(testOutput).Build();
    await sp.PopulateRegistriesAsync();

    var runner = sp.GetRequiredService<IWorkflowRunner>();
    var workflow = new MyWorkflowDefinition();

    var result = await runner.RunAsync(workflow);

    Assert.Equal(WorkflowStatus.Finished, result.WorkflowInstance!.Status);
}

Component test

[Fact]
public async Task Workflow_Persists_Instance_And_Journal()
{
    var sp = new TestApplicationBuilder(testOutput)
        .Build();

    var runner = sp.GetRequiredService<AsyncWorkflowRunner>();
    var result = await runner.RunAndAwaitWorkflowCompletionAsync(
        WorkflowDefinitionHandle.ByDefinitionId(someDefinitionId, VersionOptions.Published)
    );
    result.WorkflowExecutionContext.Status.Should().Be(WorkflowStatus.Finished);
}

Component test with async workflow execution

For testing workflows that complete asynchronously (e.g., with timers, external triggers), use AsyncWorkflowRunner:

[Fact]
public async Task Workflow_Completes_Asynchronously()
{
    var sp = new TestApplicationBuilder(testOutput).Build();
    var runner = sp.GetRequiredService<AsyncWorkflowRunner>();

    var result = await runner.RunAndAwaitWorkflowCompletionAsync(
        WorkflowDefinitionHandle.ByDefinitionId(workflowId, VersionOptions.Published)
    );

    result.WorkflowExecutionContext.Status.Should().Be(WorkflowStatus.Finished);
    result.ActivityExecutionRecords.Should().HaveCount(expectedCount);
}

AsyncWorkflowRunner tracks activity execution records and awaits workflow completion signals, making it ideal for testing asynchronous workflow behavior deterministically.


FAQ (quick pointers)

Q: How do I import workflow definitions in tests and where do I put them?

A: For JSON-defined workflows use the repo's test integration helpers (PopulateRegistriesAsync() or the test registration helpers in test/common). See integration test examples in the test tree. Leave the definitions next to the tests that use them.

Q: Which helper should I use to run a workflow?

A: Prefer IWorkflowRunner.RunAsync for in-process deterministic runs. For activities use ActivityTestFixture.

Q: How do I check persisted journal entries?

A: Query IWorkflowInstanceStore and inspect the persisted journal on the instance. Use deterministic instance id or correlation id to locate the exact instance.

Q: Do I need a new helper to wait for workflow completion?

A: No. The repo provides RunAsync for integration tests and ActivityTestFixture for activity unit tests, as well as integration helpers that cover all necessary scenarios. For async workflows in component tests, use AsyncWorkflowRunner.


Appendix — examples in the repository (where to look)

Search the test/ tree for examples that follow the above patterns: