Click here to Skip to main content
Click here to Skip to main content
Technical Blog

Windows Workflow Sequential Workflow Unit Testing

, 28 Aug 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
Windows Workflow Foundation (WWF) is a powerful programming model and designer for workflow processes. Using workflow is easier than some may believe.

Windows Workflow Foundation (WWF) is a powerful programming model and designer for workflow processes. Using workflow is easier than some may believe.

I wanted to share a quick post relative to some practices I've found useful for sequential workflows, specifically around Inversion of Control (IoC) and unit testing.

In our shop, we have several complex algorithms that are great candidates for sequential workflow. By using sequential workflow, we can:

  • Easily visualize the overall layout and flow of the process
  • Test the architecture of the flow (i.e. do we get to this node or that node) without having to worry with or deal with the underlying implementation of nodes within the flow
  • Refactor complex code simply by dragging and dropping nodes in the designer
  • Setup unit tests to ensure the integrity of the workflow persists as other systems are refactored

If you aren't familiar with workflow, I'd suggest searching for a few tutorials or walkthroughs. This is one of those technologies that is probably easier to learn by building some test projects than it is simply reading about it. The scope of this article will be how to build your sequential workflows for unit tests.

Interfaces and Delegates In

The workflow model lets you declare public properties on the main workflow that are then available to all of the nodes within that workflow. When you instantiate the workflow, you can send in a collection of name-value pairs map to public properties. For example, if I have an interface declared on my workflow:

...
public IMyInterface MyInterfaceImplemented { get; set; }
...

Then I can pass to workflow an instance of that interface like this:

...
Dictionary<string, object> myParams = new Dictionary<string, object>
   {
      {"MyInterfaceImplemented", new MyInterfaceImplementation()}
   };
...
_runtime.CreateWorkflow(typeof (MyWorkflow), myParams);

I'll get to what _runtime is in a bit. My point is, however, that any type of business logic you follow within your workflow should be hidden behind an interface and/or delegate. If you are passing in concrete instances, you are missing out on the power of being able to unit test your workflow structure.

You may have noticed that the code blocks in a sequential workflow, unless encapsulated as a custom entity, are actually events triggered by the workflow engine. For example, I might insert a Code workflow activity called AnalyzeUser. In the code behind, this appears as:

private void AnalyzeUser_ExecuteCode(object sender, EventArgs e)
{
}

In some cases, you might be calling a method or service on an existing interface. However, other cases may be simple snippets of code. In order to keep your workflow completely pluggable, consider using a delegate. For example, if the analyze user takes a user parameter and then sets a flag in the workflow based on the result, instead of this:

private void AnalyzeUser_ExecuteCode(object sender, EventArgs e)
{
   _isOver30 = User.Age > 30;            
}

Try this:

// declare a delegate to analyze the user
public delegate bool AnalyzeUserDelegate(User user); 

// declare a method to set the delegate
public AnalyzeUserDelegate AnalyzeUserMethod { get; set; }

private void AnalyzeUser_ExecuteCode(object sender, EventArgs e)
{
   _isOver30 = AnalyzeUserMethod(User); 
}

Note we've encapsulated the action that is required (set a flag by analyzing the user) but we've removed the dependency of a concrete implementation. Why would I want to do this? Perhaps I am testing the overall workflow structure and want to know when it reaches the call to the delegate. I'm not concerned about whether it sets the users age, only if the method is reached. I can easily test for that situation like this:

...
bool delegatedCalled = false;

AnalyzeUserDelegate testMethod = (user) =>
   {
      delegatedCalled = true;
      return true;
   };

myParams.Add("AnalyzeUserMethod", testMethod);
...
instance.Start(); // start the worfklow
Assert.IsTrue(delegateCalled, "The method was invoked."); 
...

This allows you to separate testing the logic/flow of the workflow from the implementation of the various nodes within the workflow.

Properties Out

We talked about how to get implementation into the workflow, now let's talk about what to get out. Even if your workflow does not "return" a value to the outside world, you should consider exposing some properties to indicate key milestones within the workflow. One of our workflows involves assigning an asset to a location. The workflow itself only needs to perform the actual assignment. There is no "output" other than the changes to the system itself. However, for testing purposes, we expose certain values such as the location assigned and a flag that indicates whether an assignment was successful. This allows us to test the outcome, like this:

...
public bool WasAssigned { get; set; }
...
instance.Start(); // start the workflow
Assert.IsTrue((bool)results["WasAssigned"]); 

That example is a simple test of whether or not a successful assignment took place.

Wiring the Tests

One thing I've noticed is that few people realize workflows are very easy to test. There are many ways to trigger a workflow (events, schedules, etc.). The standard examples I've seen online involve launching the engine in potentially a different thread altogether. Fortunately, the engine is extensible and there is a service shipped with the runtime that allows you to run it in an almost-synchronous way.

The first thing to do is set up references to the run time and the service that allows for the immediate workflow execution:

...
private WorkflowRuntime _runtime; 
private ManualWorkflowSchedulerService _scheduler; 
...

In your test setup, you'll instantiate the runtime and add the manual service:

[TestInitialize]
public void TestInitialize()
{
   _scheduler = new ManualWorkflowSchedulerService(); 
   _runtime = new WorkflowRuntime(); 
   _runtime.AddService(_scheduler); 
}

Be sure to tear down the workflow after the test is run:

[TestCleanup]
public void TestCleanup()
{
    if (_runtime != null && _runtime.IsStarted)
    {
        _runtime.StopRuntime();
    }

    if (_runtime != null)
    {
        _runtime.Dispose();
    }
}

Now you can wire up your test. If you want to be able test the output results as I mentioned above, you need to bind to the WorkflowCompleted event:

...
Dictionary<string, object> results = null;
_runtime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs wce) 
	{ results = wce.OutputParameters; };
...

This will take all public properties and dump them by name and value into the results variable for inspection. The next thing is simply to create an instance and run it, like this:

WorkflowInstance instance = _runtime.CreateWorkflow(typeof (MyWorkflow), myParams);
instance.Start();
ManualWorkflowSchedulerService schedulerService = 
	_runtime.GetService<ManualWorkflowSchedulerService>();
schedulerService.RunWorkflow(instance.InstanceId);
Assert.IsNotNull(results);

The results test ensures the workflow successfully ran.

Now I can test my workflow structure using a mocking framework like Moq. Moq makes it easy to inject a test object and verify whether or not it was called. If I have an interface called IMyUserServices and I am expecting the workflow to call a method called ValidateUser, I would simply load it up like this:

Mock<IMyUserServices> myUserServices = new Mock<IMyUserServices>(); 
Dictionary<string, object> myParams = new Dictionary<string, object> 
   {
      { "MyUserServicesImplemented", myUserServices.Object }
   };
// set up the call to always return true
myUserServices.Setup(svc => svc.ValidateUser(It.IsAny<User>())).Returns(true); 
...
instance.Start();
...
// make sure we called it
myUserServices.Verify(svc => svc.ValidateUser(It.IsAny<User>()), Times.Once());
...

Obviously going through all of the features and functionality of mock frameworks is outside of the scope of this post, but I wanted to share how easy these tools make it to create powerful, granular tests. I am able to wire up the entire complex workflow and test that the flow works as expected before ever writing a line of actual implementation. Then, it becomes simple to implement the code and write unit tests for each atomic piece of functionality. This then makes it easy to test and implement complex solutions by breaking them into simple parts.

Finally, I'll leave you with a little snippet of unit testing wisdom I recently learned. A stub is something you inject to make a test run. A mock is something you check the state of to validate your test. Often people refer to stubs as mocks, but if you aren't inspecting the stub for success, it's a stub. If you are checking it for something (state, output, exception), then it is a mock.

Jeremy Likness

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Jeremy Likness
Architect Wintellect
United States United States
Jeremy Likness is a principal consultant at Wintellect. Jeremy, an experienced entrepreneur and technology executive, has successfully helped ship commercial enterprise software for 20 years. He specializes in catalyzing growth, developing ideas and creating value through delivering software in technical enterprises. His roles as business owner, technology executive and hands-on developer provided unique opportunities to directly impact the bottom line of multiple businesses by helping them grow and increase their organizational capacity while improving operational efficiency. He has worked with several initially small companies like Manhattan Associates and AirWatch before they grew large and experienced their transition from good to great while helping direct vision and strategy to embrace changing technology and markets. Jeremy is capable of quickly adapting to new paradigms and helps technology teams endure change by providing strong leadership, working with team members “in the trenches” and mentoring them in the soft skills that are key for engineers to bridge the gap between business and technology.
Follow on   Twitter   Google+   LinkedIn

Comments and Discussions

 
QuestionHow about sharepoint workflows ? Pinmemberclement_91131-Aug-09 14:32 
AnswerRe: How about sharepoint workflows ? PinmemberJeremy Likness31-Aug-09 15:13 
Thanks! If you have a chance, please vote for the article. I appreciate you!
 
I have not worked with MOSS workflows, something to look into! Let me know if you find anything about it, and of course if I come across a solution I will post an article or blog on it!
 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.141022.1 | Last Updated 28 Aug 2009
Article Copyright 2009 by Jeremy Likness
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid