How to Unit Test WF4 Workflows
Recently I asked you to vote on topics you want to see on endpoint.tv. Coming in at #3 was this question on how to Unit Test WF4 Workflows.
Because Workflows range from very simple activities to long and complex multi-step processes this can be a very challenging. I found after writing many activities that I began to see opportunities to create a library of test helpers which became the WF4 Workflow Test Helper library.
In this post I’ll show you how you can test a simple workflow using one of the helpers from the library.
Watch endpoint.tv - How To Unit Test Workflows
Download Workflow Test Helper (including this sample code) from MSDN Code Gallery
Testing Activity Inputs / Outputs
If you have a simple activity that returns out arguments and does not do any long-running work (no messaging activities or bookmarks) then the easiest way to test it is with WorkflowInvoker.
The Workflow we are testing in this case contains an activity that adds two Int32 in arguments and returns an out argument named sum
The easiest way to test such an activity is by using Workflow Invoker
[TestMethod]
public void ShouldAddGoodSum()
{
var output = WorkflowInvoker.Invoke(new GoodSum() {x=1, y=2});
Assert.AreEqual(3, output["sum"]);
}
While this test is ok it assumes that the out arguments collection contains an argument of type Int32 named “sum”. There are actually several possibilities here that we might want to test for.
- There is no out argument named “sum” (names are case sensitive)
- There is an argument named “sum” but it is not of type Int32
- There is an argument named “sum” and it is an Int32 but it does not contain the value we expect
Any time you are testing out arguments for values you have these possibilities. Our test code will detect all three of these cases but to make it very clear exactly what went wrong I create a test helper called AssertOutArgument.
Now let’s consider what happens if you simply changed the name of the out argument from “sum” to “Sum”. From the activity point of view “Sum” is the same as “sum” because VB expressions are not case sensitive.
But our test code lives in the world of C# where “Sum” != “sum”. When I make this change the version of the test that uses Assert.AreEqual gets this error message
Test method TestProject.TestSumActivity.ShouldAddBadSumArgName threw exception:
System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary.
If you have some amount of experience with Workflow and arguments you know that the out arguments are a dictionary of name/value pairs. This exception simply means it couldn’t find the key – but what key?
Using WorklowTestHelper
- Download the binaries for WorkflowTestHelper
- Add a reference to the WorkflowTestHelper assemblies
- Add a using WorkflowTestHelper statement
To use AssertOutArgument we change the test code to look like this
[TestMethod]
public void ShouldAddGoodSumAssertOutArgument()
{
var output = WorkflowInvoker.Invoke(new GoodSum() { x = 1, y = 2 });
AssertOutArgument.AreEqual(output, "sum", 3);
}
Now the error message we get is this
AssertOutArgument.AreEqual failed. Output does not contain an argument named <sum>.
Wrong Type Errors
What if you changed the workflow so that “sum” exists but it is the wrong type? To test this I made “sum” of type string and changed the assignment activity expression to convert the result to a string.
The Assert.AreEqual test gets the following result
Assert.AreEqual failed. Expected:<3 (System.Int32)>. Actual:<3 (System.String)>.
This is ok but you do look twice – Expected:<3 then Actual:<3 but then you notice the types are different.
AssertOutArgument is more explicit
AssertOutArgument.AreEqual failed. Wrong type for OutArgument <sum>. Expected Type: <System.Int32>. Actual Type: <System.String>.
Summing It Up
AssertOutArgument covers three things, make sure the arg exists, make sure it is the correct type and make sure it has the correct value. Of course there is much more to the WF4 Workflow Test Helper that I will cover in future posts.
Comments
Anonymous
September 07, 2010
As the person that published that question, it's really cool to see you respond to it so quickly. Thanks for the article and references. I've started toying w/ the test helper and it's already proven useful.Anonymous
September 23, 2010
The comment has been removedAnonymous
September 23, 2010
The comment has been removedAnonymous
September 23, 2010
The comment has been removedAnonymous
September 23, 2010
@stevescottwork you can always use an IDictionary<string,object> to create the input arguments - that will work.