Windows Workflow Foundation (WF) defines workflows as object graphs. To save or compile workflow definitions, these object graphs must be serialized, and WF supports serialization to both XAML and code. Similar to other components, WF utilizes the .NET design-time framework for serialization and deserialization of objects. Serializing objects to code is done using CodeDomSerializer-derived classes.
In WF, the base CodeDOM serializer is ActivityCodeDomSerializer, and if you want to unit test your custom CodeDOM serializer for a custom activity, you can use the general approach I have earlier described. As it turns out, unit testing WF Activity CodeDOM serializers is simpler than unit testing components in general, since the base WF classes are already doing some of the work for you. You will still need to implement a designer loader for testing purposes, but you don't need to mess with a fake implementation of ITypeResolutionService.
Let's say that I wish to unit test my custom serialization logic for this activity and its corresponding serializer:
[DesignerSerializer(typeof(MyActivityCodeDomSerializer),
typeof(CodeDomSerializer))]
public class MyActivity : Activity
{
// Implementation goes here
}
public class MyActivityCodeDomSerializer :
ActivityCodeDomSerializer
internal class FakeCodeDomDesignerLoader : WorkflowDesignerLoader
public override string FileName
get { return "Fake"; }
public override TextReader GetFileReader(string filePath)
throw new NotImplementedException();
public override TextWriter GetFileWriter(string filePath)
When you call BeginLoad on a DesignSurface with this loader (which I'll do in the unit test), PerformLoad will be called, so I have overridden this method to define a default root component:
protected override void PerformLoad(
IDesignerSerializationManager serializationManager)
base.PerformLoad(serializationManager);
CompositeActivity rootActivity = new SequenceActivity();
this.AddActivityToDesigner(rootActivity);
To retrieve the serialized code, the designer loader must be flushed. While flushing, PerformFlush is called, so I override this method to capture the serialized code and save it in a private field:
private CodeStatementCollection codeStatements_;
protected override void PerformFlush(
base.PerformFlush(serializationManager);
ActivityCodeDomSerializer serializer =
new ActivityCodeDomSerializer();
this.codeStatements_ =
(CodeStatementCollection)serializer.Serialize(
serializationManager, this.LoaderHost.RootComponent);
internal CodeStatementCollection GetCodeStatements()
this.Flush();
return this.codeStatements_;
With FakeCodeDomDesignerLoader in place, it's now fairly straightforward to write a unit test that exercises the custom serializer that's associated with MyActivity:
[TestMethod]
public void MyTestCase()
FakeCodeDomDesignerLoader loader =
new FakeCodeDomDesignerLoader();
DesignSurface surface = new DesignSurface();
surface.BeginLoad(loader);
IDesignerHost host =
(IDesignerHost)surface.GetService(typeof(IDesignerHost));
MyActivity ma =
(MyActivity)host.CreateComponent(typeof(MyActivity),
"ma");
((SequenceActivity)host.RootComponent).Activities.Add(ma);
CodeStatementCollection codeStatements =
loader.GetCodeStatements();
// Perform validation of code statements
As with the general approach, the first thing to do is to create an instance of the designer loader. Then I create a new DesignSurface which is going to host the SequenceActivity root component that I can then add an instance of MyActivity to. Calling BeginLoad on the DesignSurface caused PerformLoad to be called on the FakeCodeDomDesignerLoader instance, which then creates the SequenceActivity and adds it as the root component.
The next step is to retrieve an instance of IDesignerHost, which can then be used to create an instance of MyActivity. Creating MyActivity instances this way ensures that any designers, editors and serializers (in this case, MyActivityCodeDomSerializer) is loaded and initialized correctly.
The newly created MyActivity instance is then added to the root component, i.e. the sequential workflow.
Getting the serialized code statements is then as simple as calling GetCodeStatements. This returns a CodeStatementCollection instance, and you can then use the principles outlined in my former article to validate that your custom serializer created the expected code statements.