Search code examples
c#nunit.net-standard.net-framework-version

Library working when called from Console app but not from NUnit test


I'm trying to evaluate a workflow library for a project I'm working on. The lib can be found at github workflow-core.

To start things up I was trying to build a simple workflow, that just writes some text to a file. The curious thing is, that the workflow works fine, when called from a console application project. But when I use the same code in an NUnit test and run it, it doesn't write anything to the file.

I'm a little lost here and don't even know which details are import for you guys to know to help me figure this out, but maybe this might be relevant?

  • The workflow-core library is build on .NET standard 2.0
  • The NUnit project and the console project both use .NET Framework 4.7.2
  • The workflow-core lib uses all kinds of Tasks (as in Task Parallel Library) stuff
  • The workflow-core lib is build with dependency injection using the Microsoft.Extensions.DependencyInjection library

And here is the relevant code: First the workflow class:

public class HelloWorldWorkflow : IWorkflow
{
    public string Id => nameof(HelloWorldWorkflow);
    public int Version => 1;
    public void Build(IWorkflowBuilder<object> builder)
    {
        builder.StartWith((context) =>
        {
            File.WriteAllText(@"C:\Test\test.txt", "Test line worked!");
            return ExecutionResult.Next();
        });
    }
}

The calling code from the console app (working):

class Program
{
    static void Main(string[] args)
    {
        var serviceCollection = new ServiceCollection();
        serviceCollection.AddLogging((config) => config.AddConsole());
        serviceCollection.AddWorkflow();

        serviceCollection.AddTransient<LogStep>();

        var serviceProvider = serviceCollection.BuildServiceProvider();

        var host = serviceProvider.GetService<IWorkflowHost>();
        host.RegisterWorkflow<HelloWorldWorkflow>();

        host.Start();

        host.StartWorkflow(nameof(HelloWorldWorkflow));

        Console.WriteLine("Done");
        Console.ReadLine();

        host.Stop();
    }
}

And the code from my test project (not working):

[TestFixture]
public class ExplorationTests
{
    private ServiceProvider _serviceProvider;
    private IWorkflowHost _host;

    [OneTimeSetUp]
    public void Init()
    {
        var serviceCollection = new ServiceCollection();
        serviceCollection.AddLogging((config) => config.AddConsole());
        serviceCollection.AddWorkflow();

        serviceCollection.AddTransient<LogStep>();
        serviceCollection.AddTransient<HelloWorldWorkflow>();

        _serviceProvider = serviceCollection.BuildServiceProvider();

        _host = _serviceProvider.GetService<IWorkflowHost>();
        _host.RegisterWorkflow<HelloWorldWorkflow>();

        _host.Start();
    }

    [Test]
    public void Test()
    {
        _host.StartWorkflow(nameof(HelloWorldWorkflow));

    }

    [OneTimeTearDown]
    public void TearDown()
    {
        _host.Stop();
    }
}

I'd be glad for any clues on how to figure this out.


Solution

  • Execution of workflows is asynchronous, therefore you have to wait for some kind of event to occur which signals the completion. Otherwise your test teardown will kill the host before the workflow has the chance to do anything.

    This answer's first version contained:

    Adding .Wait() or one of its overloadings (which allow to specify a maximum duration to wait) to the result of StartWorkflow to block the test until the workflow has completed.

    Unfortunately that's wrong as StartWorkflow returns a Task yielding only the ID of the workflow instance. When this task is resolved your workflow probably hasn't done anything meaningful.

    There is a feature request on GitHub asking for the desired feature: Wait for workflow to finish

    Until that request is resolved, you may help yourself by creating a ManualResetEvent or maybe AutoResetEvent and putting it somewhere your final workflow step can access and call .Set() on it. Your test should wait for that by calling .WaitOne() on it (this is blocking).

    Another event which might be sufficient (but inefficient) is just having waited a long enough duration: Thread.Sleep(2000) waits two seconds. Please be aware that even after that it's possible that your workflow has not completed due to the asynchronous nature of the workflow executor.