OdeToCode IC Logo

Unit Testing Workflow Activities

Thursday, August 3, 2006

I've been kicking around the best approach for unit testing a custom activity for Windows Workflow. I haven't found an approach I'm comfortable with as yet.

Here is a simple, contrived custom activity.

using System;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Serialization;

[assembly:
XmlnsDefinition("https://odetocode.com/wf/activities",
                          
"OdeToCode.WF.Activities")]
namespace OdeToCode.WF.Activities
{
   
public class CreateMessageActivity : Activity
   {

    
private string _recipient;
    
public string Recipient
    {
      
set { _recipient = value; }
    }

    
private string _message;
    
public string Message
    {
      
get { return _message; }
    }
   
    
protected override ActivityExecutionStatus
      Execute(
ActivityExecutionContext executionContext)
    {

      _message =
String.Format("Message to {0}", _recipient);
      
return ActivityExecutionStatus.Closed;
    }
   }
}

It's tempting to create a unit test that instantiates the activity and calls Execute directly, but most activities are not this simple. The ActivityExecutionContext class is sealed, and mocking out all the scheduling services, queues, and other dependencies inside is a task for Sisyphus.

Another approach is to test the activity using the WF runtime. A little bit of hacking produces:

[TestFixture]
public class CreateMessageActivityTests
{
  [
TestFixtureSetUp]
  
public void StartWfRuntime()
  {
    _runtime =
new WorkflowRuntime();
    
    
// use manual scheduler for synchronous execution
    _scheduler = new ManualWorkflowSchedulerService();
    _runtime.AddService(_scheduler);

    
// type provider needed to resolve custom activity in XAML
    TypeProvider typeProvider = new TypeProvider(_runtime);
    typeProvider.AddAssembly(
Assembly.Load("ActivityLibrary2"));
    _runtime.AddService(typeProvider);

    _runtime.WorkflowCompleted +=
        
new EventHandler<WorkflowCompletedEventArgs>
          (_runtime_WorkflowCompleted);
    _runtime.WorkflowTerminated +=
        
new EventHandler<WorkflowTerminatedEventArgs>
          (_runtime_WorkflowTerminated);
  }

  [
TestFixtureTearDown]
  
public void ShutdownWfRuntime()
  {
    _runtime.StopRuntime();
    _runtime.Dispose();
  }

  [
SetUp]
  
public void SetupTest()
  {
    _exception =
null;
    _outputs =
null;
  }

  
void _runtime_WorkflowTerminated(
      
object sender,
      
WorkflowTerminatedEventArgs e
    )
  {
    _exception = e.Exception;
  }

  
void _runtime_WorkflowCompleted(
      
object sender,
      
WorkflowCompletedEventArgs e
    )
  {
    _outputs = e.OutputParameters;
  }

  
WorkflowRuntime _runtime = null;
  
ManualWorkflowSchedulerService _scheduler;
  
Dictionary<string, object> _outputs;
  
Exception _exception;

  
// create custom activity as the root of a workflow
  string _xaml =
    
@"<otc:CreateMessageActivity
         xmlns:otc=""https://odetocode.com/wf/activities"">
      </otc:CreateMessageActivity>"
;
}

This is a lot of code, and I haven't even written a test yet. Unit testing a custom activity is an indirect process because the workflow runtime tries to shield a workflow instance from the callous hands of the outside world.

[Test]
public void CreatesMessageForScott()
{
  
XmlReader reader = XmlReader.Create(
                      
new StringReader(_xaml)
                     );

  
Dictionary<string, object> parameters;
  parameters =
new Dictionary<string, object>();
  parameters.Add(
"Recipient", "Scott");

  
WorkflowInstance instance;
  instance = _runtime.CreateWorkflow(reader,
null, parameters);
  instance.Start();
  _scheduler.RunWorkflow(instance.InstanceId);

  
Assert.IsNull(_exception, "Workflow threw an exception");
  
Assert.AreEqual(_outputs.Count, 1);

  
// .. and so on

}

I'm feeding a XAML definition of a workflow to the runtime in an attempt to simplify the process and isolate the activity. Still, this code is still far too lengthy and complex to maintain in a unit test. My plan is to create a project of utility classes to simplify the code even further, but I'm still apprehensive.

Anyone else unit testing activities or workflows?