How to test WF4 Workflows with Microsoft.Activities.Simulation

Recently I posted a new sample for the CancellationScope activity.  Of course, I’m building these samples on .NET 4 Beta 1.  But the team is already making rapid progress towards the next major milestone.  They are checking in changes that may or may not break my sample applications and hands on labs.  How will I know if things are broken?

Most people rely on manual testing to detect broken code.  The problem is that manual testing is time consuming and expensive.  I wanted to find a way to unit and acceptance test the sample applications and labs.  There are significant challenges to this but I’ve got some ideas that I think will work and I’m going to describe them to you on my blog as I get them in place.

Unit Testing A Workflow

I’ve covered this on my blog in the past.  In fact, the WF4 Hands On Labs (Lab 1) from the Visual Studio 2010 training kit includes unit testing.  One issue I did not cover was how to use a Test Double with workflow.  Why would you do this?  Because some activities do things that are not testable or desirable in a unit test.

  • The WriteLine activity writes text to the console
  • A Send activity sends a message to an endpoint that might not be available when testing
  • A Receive activity requires a message to cause the workflow to continue.

One response to things like this is to use Dependency Injection with Inversion of Control containers to replace these troublesome components with something more testable.  The Microsoft patterns & practices group has produced the Unity container for this very purpose.

These solutions do not work with workflow activities because they rely on having some measure of control over the creation of dependencies so they can resolve them at runtime. 

Microsoft.Activities.Simulation

I found a way to get around this specifically for workflow.  I’m using the term “Simulation” to describe it because in the long run I believe we can build something that would be useful to technical business users for testing workflows.  To understand how it works consider the CancellationScope activity sample.  If I want to test this sample, how would I do it?

The sample writes data to a console window to show how the cancellation scope allows you to execute activities that will be triggered when a workflow element is cancelled.  It uses the System.Activities.Statements.WriteLine activity to do this.  So what I need is a way to verify that the correct messages are output by WriteLine in the correct order.  What I really wanted to do was to replace WriteLine with another activity I call MockWriteLine.

The strategy I use is to modify the XAML for testing.  Consider the following XAML snippet.

    1: <p:CancellationScope DisplayName="Cancellation Scope 1">
    2:     <p:CancellationScope.CancelHandler>
    3:       <p:WriteLine DisplayName="WriteLine Cancel Scope 1">["Cancelling scope 1..."]</p:WriteLine>
    4:     </p:CancellationScope.CancelHandler>

Here  you can see the solution staring you in the face.  If you could just change <p:WriteLine to <p:MockWriteLine and made the MockWriteLine activity have the same properties as WriteLine then you can do whatever you want from MockWriteLine to make the workflow testable.

Great, but what should MockWriteLine do?  I want to capture what it would have written to the console.  I need to get data from this activity without modifying the workflow more than I already have.  The solution is to use the Test Spy pattern with a workflow extension.

I created a class called TestConsole that will store up the WriteLine output in a buffer that I can verify later.  I can then add this extension to the workflow instance.

    1: WorkflowInstance workflowInstance = new WorkflowInstance(wfElement, inputs);
    2: TestConsole testConsole = new TestConsole();
    3:  
    4: // Add the testConsole to the extensions collection
    5: workflowInstance.Extensions.Add(testConsole);
    6:  
    7: // Run the test...
    8: RunTheTest();
    9:  
   10: // Check the first line of the buffer
   11: Assert.AreEqual("Some text to verify", testConsole.Buffer.ElementAt(0));

There is much more to say about this but I’m running out of time.  To see it in action, check out the updated version of the CancellationScope Activity Sample.