OdeToCode IC Logo

Unit Testing Workflow Activities

Thursday, August 3, 2006 by scott

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?

Something You Don't Want To See In Your Event Log

Wednesday, August 2, 2006 by scott

Source: MSSQL Server
17052 : Cannot recover the master database. Exiting.

The chkdsk result was ugly, too.....

What's Wrong With This Code?

Tuesday, August 1, 2006 by scott

The developer who wrote the following code expects to see "Hello World!" printed twice. What will the developer see, and why? (Hint: "Hello, World!" will only appear once).

using System;

class Program
{
    
static void Main()
    {
        
Console.WriteLine(Child.Message);
        
new Child();
        
Console.WriteLine(Child.Message);
    }
}

class Parent
{
    
static Parent()
    {
        _message =
"Hello!";
    }

    
static public string Message
    {
        
get { return _message; }
    }

    
static protected string _message;
}

class Child : Parent
{
    
static Child()
    {
        _message =
"Hello, World!";
    }
}

Package Pages, User Controls, and Master Pages for Reuse

Monday, July 31, 2006 by scott

Graham Dyson has an interesting project named WAPUL:

"WAPUL is a part of a project I've been developing for creating libraries of user controls, pages and master pages (although currently no support for a master page using another master page). Its aim is to avoid bodging or extra leg work when distributing (i.e. just one .dll file)."

Sharing controls and pages is a feature many people are looking for. Graham has a clever solution using code, a post-build step, and the aspnet_merge tool.

Dependency Property Notes

Friday, July 28, 2006 by scott

I've run into dependency properties in both Windows Presentation Foundation and Windows Workflow Foundation. In trying to understand what they do, I ran across Drew Marsh's excellent introduction - "Avalon: Understanding DependencyObject and DependencyProperty". Drew's article has a WPF slant and I've mostly been seeing dependency properties from the workflow side.

In WF, dependency properties enable three features:

  • Attached properties
  • Activity binding
  • Meta properties

The attached property story in WF is similar to the attached property story in WPF - a parent object can add additional state to the children it manages. A WPF grid will attach Row and Column properties to its children. The dynamically attached properties allow the grid to store layout state in each child. In WF, the Conditioned Activity Group can attach a When property to each child. The When property holds a condition for the CAG to evaluate before running a child activity.

Activity binding gives a dependency property an ActivityBind object to evaluate when asked for its value. The ActivityBind can point to other fields, properties, or methods of activities inside a workflow. One activity could bind its input properties to a previous activity's outputs. Binding makes the data flow a part of the workflow model.

The behavior of meta properties caught me off guard. There are two types of dependency properties in WF activities: meta properties and instance properties. We set meta properties at design time and cannot change their value at runtime (they can't use binding, either). I think meta properties exist to guarantee the integrity of an activity. At runtime, I can't change the InterfaceType of a CallExternalMethod activity and screw up the rest of the parameter bindings because InterfaceType is a meta-property.  

Dependency properties are easier to understand once you see the use cases. You have to wonder if we'd be talking about dependency properties a all if the mainstream CLR languages were more dynamic.

Review of the Creative Zen Vision:M

Thursday, July 27, 2006 by scott

I'm the owner of a new 30GB Zen Vision:M. The Vision:M is a portable media center and plays both MP3s and videos. The unit currently retails between $269 and $299.

The 320x240 2.5'' LCD display looks remarkably good, even with the brightness setting at 50%. The Vision:M is a bit thicker and heavier compared to its iCompetitor, but has more color (262k versus 65k), and supports more file formats (DivX, XviD, MPEG 1/2/4, and WMV all play). I'll take function over form. The rechargeable Li-Ion battery lasted over 10 hours on a mix of video and audio (mostly audio).

I did not install the bundled software. Windows Media Player 10 recognized the device and synchronized content without any fuss. I've synched music, TV shows from a Media Center, and movies in WMV and DivX format. Of course, not everything you can record with Media Center will synch with an external device, thanks to the pinheads at the MPAA.

The only problem I've had is with an MSDN web cast. It seems there is an extra stream in the web casts and the stream confuses WMP. Fortunately, Marauderz M2PMCEncoderZX converts the MSDN file, and adds some additional options for anyone who is unhappy with the default conversion settings WMP uses for portable media centers.

The Vision:M synchronizes and charges through a USB connection, but requires a small dongle to adapt the USB connector to the unit itself. A full USB re-charging takes about 4 hours. The dongle includes connections for a charging with a DC adapter, and an A/V out. Creative made life more difficult (and more expensive) by not using a standard camcorder pinout for the 3.5mm AV jack. A little bit of splicing can work around this problem.

A touch pad on the front scrolls through menus. It can take some time to become accustomed to the touchpad, but the sensitivity is adjustable. The Vision:M also includes a microphone and FM stereo receiver (but no line input). A PIM is included that will sych with Outlook, although I haven't tried this feature (it requires an install of the bundled software, which I've have not tried).

Overall, thumbs up for the Creative Zen Vision:M.

Narrative JavaScript

Wednesday, July 26, 2006 by scott

I stumbled across Narrative JavaScript this weekend.

NJS uses a compiler to generate asynchronous JavaScript from procedural JavaScript. You write simple code with local variables and blocking operations. NJS takes the simple code and spits out JavaScript with all the callbacks and asynchronous goo. It's like programming with continuations.

I wonder if Nikhil could add something like this to ScriptSharp.