Unit testing asynchronous workflow activities

In a previous blog post I demonstrated how TypeMock allowed us to mock out the workflow runtime infrastructure and create true unit tests for a workflow activity. Now this may not seems like a big deal but because most of the classes in the workflow runtime are sealed mocking them with another mocking framework is pretty much impossible. Now you can test custom activities by wrapping them in a test workflow, creating a workflow runtime, running the workflow and inspecting the result I can hardly call this a unit test as we have to create lots of, complex, depended objects. So TypeMock really opens up some new possibilities here.

But the previous examples where still pretty simple, although mocking the ActivityExecutionContext has always been considered impossible, so lets try something more complicated in the shape of an asynchronous activity. Basically an asynchronous activity is an activity that returns ActivityExecutionStatus.Executing from the Execute method indicating to the workflow runtime that it isn’t done yet. The activity uses ActivityExecutionContext at a later point in time to indicate that it is done by calling the CloseActivity function.

Lets take a quick look at our new activity.

public partial class WriteLineActivity3 : Activity
{
    public WriteLineActivity3()
    {
        InitializeComponent();
    }

    public string Message { get; set; }

    protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
    {
        ActivityExecutionStatus result = ActivityExecutionStatus.Closed;

        if (!string.IsNullOrEmpty(Message))
        {
            IComparable queueName = Guid.NewGuid();
            WorkflowQueuingService wqs = executionContext.GetService<WorkflowQueuingService>();
            WorkflowQueue queue = wqs.CreateWorkflowQueue(queueName, true);
            queue.QueueItemAvailable += new EventHandler<QueueEventArgs>(queue_QueueItemAvailable);


            WriteLineService3 service = executionContext.GetService<WriteLineService3>();

            service.WriteLine(Message, queueName);
            result = ActivityExecutionStatus.Executing;
        }

        return result;
    }

    void queue_QueueItemAvailable(object sender, QueueEventArgs e)
    {
        ActivityExecutionContext executionContext = sender as ActivityExecutionContext;
        executionContext.CloseActivity();
    }
}

In this case the return value from the Execute depends on the Message property being filled or not so we need to test this. First a simple test for an activity with an empty message:


 

/// <summary>
///A test for Execute
///</summary>
[TestMethod()]
[DeploymentItem("WFUnitTest1.exe")]
[VerifyMocks()]
public void ExecuteTestEmpty()
{
    WriteLineActivity3_Accessor target = new WriteLineActivity3_Accessor();
    ActivityExecutionStatus expected = ActivityExecutionStatus.Closed;
    ActivityExecutionStatus actual;

    ActivityExecutionContext executionContext = 
RecorderManager.CreateMockedObject<ActivityExecutionContext>(); WorkflowQueuingService wqs = RecorderManager.CreateMockedObject<WorkflowQueuingService>(); WorkflowQueue queue = RecorderManager.CreateMockedObject<WorkflowQueue>(); WriteLineService3 service = RecorderManager.CreateMockedObject<WriteLineService3>(); using (RecordExpectations recorder = RecorderManager.StartRecording()) { executionContext.GetService<WriteLineService3>(); recorder.FailWhenCalled(); } target.Message = ""; actual = target.Execute(executionContext); Assert.AreEqual(expected, actual); }

In this test I actually check that the runtime service, called WriteLineService3 in this case, is never retrieved. This test is still pretty simple and no more complicated than the test we did previously.


 


Lets take a look at something more complicated. The following test has the Message property set so it is expected to call the runtime service. But being an asynchronous activity it is also required to create a WorkflowQueue and bind to its QueueItemAvailable event so we will check that has happened. Finally this time the Execute function should return a status of ActivityExecutionStatus.Executing as it isn’t done yet. This test runs like this:

/// <summary>
///A test for Execute
///</summary>
[TestMethod()]
[DeploymentItem("WFUnitTest1.exe")]
[VerifyMocks()]
public void ExecuteTestStart()
{
    WriteLineActivity3_Accessor target = new WriteLineActivity3_Accessor();
    ActivityExecutionStatus expected = ActivityExecutionStatus.Executing;
    ActivityExecutionStatus actual;


    ActivityExecutionContext executionContext = 
        RecorderManager.CreateMockedObject<ActivityExecutionContext>();
    WorkflowQueuingService wqs = RecorderManager.CreateMockedObject<WorkflowQueuingService>();
    WorkflowQueue queue = RecorderManager.CreateMockedObject<WorkflowQueue>();
    WriteLineService3 service = RecorderManager.CreateMockedObject<WriteLineService3>();

    using (RecordExpectations recorder = RecorderManager.StartRecording())
    {
        // Record methods here
        executionContext.GetService<WorkflowQueuingService>();
        recorder.Return(wqs);
        wqs.CreateWorkflowQueue(null, true);
        recorder.Return(queue);
        queue.QueueItemAvailable += delegate(object sender, QueueEventArgs e) { };

        executionContext.GetService<WriteLineService3>();
        recorder.Return(service);
        service.WriteLine(null, null);
    }

    target.Message = "A message";
    actual = target.Execute(executionContext);

    Assert.AreEqual(expected, actual);
A little more complicated than the previous check but not bad at all [:)]

In this case we are binding to the QueueItemAvailable event so we need to add another test for the event handler and make sure it informs the workflow runtime we are done.

/// <summary>
///A test for queue_QueueItemAvailable
///</summary>
[TestMethod()]
[DeploymentItem("WFUnitTest1.exe")]
[VerifyMocks()]
public void queue_QueueItemAvailableTest()
{
    WriteLineActivity3_Accessor target = new WriteLineActivity3_Accessor();
    QueueEventArgs e = null; // TODO: Initialize to an appropriate value

    ActivityExecutionContext executionContext = 
        RecorderManager.CreateMockedObject<ActivityExecutionContext>();

    using (RecordExpectations recorder = RecorderManager.StartRecording())
    {
        executionContext.CloseActivity();
    }

    target.queue_QueueItemAvailable(executionContext, e);
}

Not bad at all [:)]


That leaves us with writing a unit test for the runtime service itself. This runtime service actually looks like this:

public class WriteLineService3: WorkflowRuntimeService
{
    public void WriteLine(string message, IComparable queueName)
    {
        Guid instanceId = WorkflowEnvironment.WorkflowInstanceId;

        ThreadPool.QueueUserWorkItem(state => WriteLineAsync(message, instanceId, queueName));
    }

    private void WriteLineAsync(string message, Guid instanceId, IComparable queueName)
    {
        Thread.Sleep(TimeSpan.FromSeconds(5));

        Console.WriteLine(message);

        WorkflowInstance instance = GetWorkflow(instanceId);
        instance.EnqueueItem(queueName, null, null, null);
    }

    private WorkflowInstance GetWorkflow(Guid instanceId)
    {
        WorkflowInstance result = Runtime.GetWorkflow(instanceId);
        return result;
    }
}

Not very complicated or realistic for that matter but still lets write a quick unit test for it like this:

/// <summary>
///A test for WriteLine
///</summary>
[TestMethod()]
[VerifyMocks(10000)]
public void WriteLineTest()
{
    WriteLineService3_Accessor target = new WriteLineService3_Accessor(); 
    
    string message = string.Empty; // TODO: Initialize to an appropriate value
    IComparable queueName = null; // TODO: Initialize to an appropriate value

    WorkflowInstance instance = RecorderManager.CreateMockedObject<WorkflowInstance>();

    using (RecordExpectations recorder = RecorderManager.StartRecording())
    {
        Guid instanceId = WorkflowEnvironment.WorkflowInstanceId;
        recorder.Return(Guid.NewGuid());

        target.GetWorkflow(instanceId);
        recorder.Return(instance);
        instance.EnqueueItem(null, null, null, null);
    }

    target.WriteLine(message, queueName);

}

The most important thing here is that we can mock the WorkflowInstance object just as easy as the ActivityExecutionContext in the previous tests. I am actually telling TypeMock to wait for 10 seconds with the [VerifyMocks(10000)] attribute. This allows the delay in the service to remain there and still check if the expected response was queued. One thing to note is that I had to create an extra GetWorkflow() function to wrap the real function on the WorkflowRuntime object. I was unable to mock the latter however I am unsure why this is the case and if mocking it is really impossible.


 


Enjoy!



www.TheProblemSolver.nl
http://wiki.WindowsWorkflowFoundation.eu

One thought on “Unit testing asynchronous workflow activities

  1. At FIRST, CONGRATS, ABOUT THE ARTICLE. THE DOMAIN OF THE WORKFLOWS COMBINED WITH AN OO APPROACH (CLASSES ETC) SEEMS QUITE REALISTIC.

    Secondly, questions of are those that have to be
    resolved indeed.

Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>