Comments (3)
I guess you could roll out your own version of SyncWorkflowRunner
, with a method AdvanceSingleStep
to achieve this
from workflow-core.
Thx for the tip!
I was successful using an own implementation of the IWorkflowExecutor.
Now the workflow can be run stepwise... perfect for unittesting when setting mocks and verifications per step.
Here is my solution:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using WorkflowCore.Interface;
using WorkflowCore.Models;
using WorkflowCore.Services;
namespace WorkflowCoreStepwiseWorkflow;
/// <summary>
/// Test stepwise workflow execution
/// </summary>
internal class Program3
{
static async Task Main(string[] args)
{
File.Delete("mydb.db");
var services = new ServiceCollection();
services.AddLogging(x => x.AddConsole());
services.AddTransient<LogStep>();
services.AddWorkflow(x => { x.UseSqlite("Data Source=mydb.db", true); });
services.AddSingleton<IWorkflowExecutor, StepwiseWorkflowExecutor>();
var serviceProvider = services.BuildServiceProvider();
var host = serviceProvider.GetRequiredService<IWorkflowHost>();
host.RegisterWorkflow<TestWorkflow, TestWorkflowData>();
host.OnStepError += Host_OnStepError;
host.Start();
var workflowId = await host.StartWorkflow("TestWorkflow", new TestWorkflowData());
Console.WriteLine($"Started workflow: {workflowId}");
var workflowExecutor = (StepwiseWorkflowExecutor)serviceProvider.GetRequiredService<IWorkflowExecutor>();
var timeout = TimeSpan.FromSeconds(10);
WaitForKey();
workflowExecutor.ExecuteNextStep(timeout);
WaitForKey();
workflowExecutor.ExecuteNextStep(timeout);
WaitForKey();
workflowExecutor.ExecuteNextStep(timeout);
WaitForKey();
workflowExecutor.ExecuteNextStep(timeout);
WaitForKey();
workflowExecutor.ExecuteRemainingSteps();
}
private static void WaitForKey()
{
Console.WriteLine("Press key to continue");
Console.ReadKey();
}
private static void Host_OnStepError(WorkflowInstance workflow, WorkflowStep step, Exception exception)
{
Console.WriteLine("Workflow Error: " + exception.Message);
}
class TestWorkflow : IWorkflow<TestWorkflowData>
{
public string Id => "TestWorkflow";
public int Version => 1;
public void Build(IWorkflowBuilder<TestWorkflowData> builder)
{
builder
.StartWith<LogStep>()
.Input(x => x.Number, x => 1)
.Then<LogStep>()
.Input(x => x.Number, x => 2)
.Then<LogStep>()
.Input(x => x.Number, x => 3)
.Then<LogStep>()
.Input(x => x.Number, x => 4);
}
}
class LogStep : IStepBody
{
public int Number { get; set; }
public async Task<ExecutionResult> RunAsync(IStepExecutionContext context)
{
Console.WriteLine($"LogStep: {Number}");
return ExecutionResult.Next();
}
}
class TestWorkflowData
{
}
}
/// <summary>
/// Issue: https://github.com/danielgerlag/workflow-core/issues/1190
/// Wraps default implementation: https://github.com/danielgerlag/workflow-core/blob/master/src/WorkflowCore/Services/WorkflowExecutor.cs
///
/// Register via: services.AddSingleton<IWorkflowExecutor, StepwiseWorkflowExecutor>();
/// </summary>
public class StepwiseWorkflowExecutor : IWorkflowExecutor
{
private readonly WorkflowExecutor _wrapped;
private readonly Semaphore _executeStep = new(0, int.MaxValue);
private readonly Semaphore _executedStep = new(0, 1);
public StepwiseWorkflowExecutor(
IWorkflowRegistry registry,
IServiceProvider serviceProvider,
IScopeProvider scopeProvider,
IDateTimeProvider datetimeProvider,
IExecutionResultProcessor executionResultProcessor,
ILifeCycleEventPublisher publisher,
ICancellationProcessor cancellationProcessor,
WorkflowOptions options,
ILoggerFactory loggerFactory)
{
_wrapped = new WorkflowExecutor(registry,
serviceProvider, scopeProvider, datetimeProvider,
executionResultProcessor,
publisher,
cancellationProcessor,
options,
loggerFactory);
}
public async Task<WorkflowExecutorResult> Execute(
WorkflowInstance workflow,
CancellationToken cancellationToken = new())
{
while (!_executeStep.WaitOne(100))
cancellationToken.ThrowIfCancellationRequested();
var workflowExecutorResult = await _wrapped.Execute(workflow, cancellationToken);
_executedStep.Release(1);
return workflowExecutorResult;
}
public bool ExecuteNextStep(TimeSpan timeout, bool throwIfTimeout = true)
{
_executeStep.Release(1);
var result = _executedStep.WaitOne(timeout);
if (throwIfTimeout && !result)
throw new InvalidOperationException("Timeout when waiting for next workflow step");
return result;
}
/// <summary>
/// WaitFor consists of 2 steps
/// </summary>
public bool ExecuteNextWaitFor(TimeSpan timeout, bool throwIfTimeout = true)
{
if (!ExecuteNextStep(timeout)) // WaitFor
return false;
return ExecuteNextStep(timeout); // Receive
}
/// <summary>
/// Decide/Branch contains of 1 step
/// </summary>
public bool ExecuteNextDecideBranch(TimeSpan timeout, bool throwIfTimeout = true)
{
return ExecuteNextStep(timeout);
}
public void ExecuteRemainingSteps()
{
// let all remaining steps run to end
_executeStep.Release(int.MaxValue / 2);
}
}
Console output:
info: WorkflowCore.Services.WorkflowHost[0]
Starting background tasks
Started workflow: f2c41576-0fb2-4072-81cd-3f5a551ec7e1
Press key to continue
LogStep: 1
Press key to continue
LogStep: 2
Press key to continue
LogStep: 3
Press key to continue
LogStep: 4
Press key to continue
from workflow-core.
For reference: I created a base class for my unittests in case someone else needs something similar:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Moq;
using WorkflowCore.Interface;
using WorkflowCore.Models;
using WorkflowCore.Testing;
using WorkflowCoreStepwiseWorkflow;
using Xunit;
using Xunit.Abstractions;
namespace WorkflowCoreStepwiseTestBaseClass;
public class WorkflowTestBase<TWorkflow, TData> : WorkflowTest<TWorkflow, TData>
where TWorkflow : IWorkflow<TData>, new()
where TData : class, new()
{
private readonly ITestOutputHelper _testOutputHelper;
protected TimeSpan TestTimeout { get; } = TimeSpan.FromSeconds(System.Diagnostics.Debugger.IsAttached ? 30 : 3);
private StepwiseWorkflowExecutor _workflowExecutor = null!; // will be set in setup
public WorkflowTestBase(ITestOutputHelper testOutputHelper)
{
_testOutputHelper = testOutputHelper;
}
/// <summary>
/// We need to access the ServiceProvider
/// Method copied from: https://github.com/danielgerlag/workflow-core/blob/master/src/WorkflowCore.Testing/WorkflowTest.cs
/// </summary>
protected override void Setup()
{
var services = new ServiceCollection();
services.AddLogging();
ConfigureServices(services);
var serviceProvider = services.BuildServiceProvider();
PersistenceProvider = serviceProvider.GetRequiredService<IPersistenceProvider>();
Host = serviceProvider.GetRequiredService<IWorkflowHost>();
Host.RegisterWorkflow<TWorkflow, TData>();
Host.OnStepError += Host_OnStepError;
Host.Start();
_workflowExecutor = (StepwiseWorkflowExecutor)serviceProvider.GetRequiredService<IWorkflowExecutor>();
}
protected override void ConfigureServices(IServiceCollection services)
{
base.ConfigureServices(services);
services.AddLogging(x =>
x.AddConsole());
services.AddSingleton<IWorkflowExecutor, StepwiseWorkflowExecutor>();
services.AddTransient<LogStep>();
}
protected void TestStep(string stepName)
{
_testOutputHelper.WriteLine($"Testing step '{stepName}'...");
}
protected void TestWaitFor(string waitName)
{
_testOutputHelper.WriteLine($"Testing wait for '{waitName}'...");
}
protected void TestDecideBranch(string decideName)
{
_testOutputHelper.WriteLine($"Testing decide/branch '{decideName}'...");
}
protected bool ExecuteNextStep(TimeSpan timeout, bool throwIfTimeout = true)
{
return _workflowExecutor.ExecuteNextStep(timeout, throwIfTimeout);
}
protected bool ExecuteNextWaitFor(TimeSpan timeout, bool throwIfTimeout = true)
{
return _workflowExecutor.ExecuteNextWaitFor(timeout, throwIfTimeout);
}
protected bool ExecuteNextDecideBranch(TimeSpan timeout, bool throwIfTimeout = true)
{
return _workflowExecutor.ExecuteNextDecideBranch(timeout, throwIfTimeout);
}
}
public class TestWorkflowTests : WorkflowTestBase<TestWorkflow, TestWorkflowData>
{
private readonly Mock<IMonitor> _monitor;
public TestWorkflowTests(ITestOutputHelper testOutputHelper)
: base(testOutputHelper)
{
_monitor = new Mock<IMonitor>();
}
protected override void ConfigureServices(IServiceCollection services)
{
base.ConfigureServices(services);
services.AddTransient(_ => _monitor.Object);
}
[Fact]
public async Task SimpleTest()
{
// Arrange
Setup();
// Act
var data = new TestWorkflowData();
var workflowId = await StartWorkflowAsync(data);
TestStep(nameof(LogStep));
ExecuteNextStep(TestTimeout);
_monitor.Verify(x => x.Execute(1));
TestStep(nameof(LogStep));
ExecuteNextStep(TestTimeout);
_monitor.Verify(x => x.Execute(2));
TestStep(nameof(LogStep));
ExecuteNextStep(TestTimeout);
_monitor.Verify(x => x.Execute(3));
TestStep(nameof(LogStep));
ExecuteNextStep(TestTimeout);
_monitor.Verify(x => x.Execute(4));
}
}
public class TestWorkflow : IWorkflow<TestWorkflowData>
{
public string Id => "TestWorkflow";
public int Version => 1;
public void Build(IWorkflowBuilder<TestWorkflowData> builder)
{
builder
.StartWith<LogStep>()
.Input(x => x.Number, x => 1)
.Then<LogStep>()
.Input(x => x.Number, x => 2)
.Then<LogStep>()
.Input(x => x.Number, x => 3)
.Then<LogStep>()
.Input(x => x.Number, x => 4);
}
}
public class LogStep : IStepBody
{
private readonly IMonitor _monitor;
public int Number { get; set; }
public LogStep(IMonitor monitor)
{
_monitor = monitor;
}
public async Task<ExecutionResult> RunAsync(IStepExecutionContext context)
{
Console.WriteLine($"LogStep: {Number}");
_monitor.Execute(Number);
return ExecutionResult.Next();
}
}
public interface IMonitor
{
void Execute(int number);
}
public class TestWorkflowData
{
}
from workflow-core.
Related Issues (20)
- How to achieve a better process rollback workflow HOT 4
- How to achieve exponential backoff retry policy HOT 1
- Facing issue to use Mongo as persistance storage HOT 2
- On wpf application workflow doesn't run at first HOT 3
- How to rollback service Transaction
- host.Start()启动后的host对象和从容器获取的IWorkflowHost对象不一样
- How to register workflows on every node with Multi-node clusters?
- Clear terminated workflow instances
- 7k requests a minute to azure blob storage
- Memory leak
- [Question] Would it be advisable to structure our process using sub-workflows? HOT 3
- Deserialisation in the ToWorkflowInstance() duplicates List items due to not set ObjectCreationHandling = ObjectCreationHandling.Replace in the JsonSerializerSettings
- [Question] How to break foreach loop?
- Is there any sample to use it on .NET framework?
- [Question] What does the option UseMaxConcurrentWorkflows do?
- [Question] Best practices for handling events and error reporting in ASP.NET Core Web API HOT 1
- [Question]:How to set UTC time as local time?
- StepBuilder.Output method fails to map entities
- If I don't know the ID, how should I get all WorkFlowInstanse and its status HOT 1
- [Question]:Security Risk in TypeNameHandling.All in JsonSerializerSettings
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from workflow-core.