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 every 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.