次の方法で共有


Unit Testing Workflow Activities with Moles

  • The Why: True unit testing of Workflow Activities is a pain if not impossible.
  • The How: Use Moles CLR Detour technologies developed by Microsoft Research to isolate the system under test.
  • The What: A sample C# project that showcases a handful of core Workflow unit test scenarios.

I’ve been working with Workflow Foundation for a few year now and I’ve become a huge fan of the technology. Working in the enterprise software space I find it to be a natural fit as a solution for a great deal of scenarios. But (there is always a but) if you have ever been charged with the task of unit testing workflow activities or practice TDD you are probably familiar with how frustrating it can be. For what ever reason the Workflow team wasn’t overly concerned with unit test-ability and in my opinion were overly aggressive in marking constructors as internal and the like. Unit testing is primarily concerned with testing the smallest unit of functionality possible which is typically a single method. With Workflow Activities this is very difficult. For example you may want to unit test the Method “Execute” on your CodeActivity which takes an instance of CodeActivityContext, but the constructor is marked internal and it doesn’t implement an interface that we can leverage to create Stand-In classes. The usage of interfaces to create and pass in Stand-In classes is my preferred method of isolating the system under test to small testable chunks so I find this especially troublesome. But since that options isn’t available to us when we are working with Activities we must do something else. One option I’ve found useful is an approach the uses Moles from Microsoft Research that is based on CLR Detours.

Moles

Moles is part of the Pex & Moles project from MSR that leverages a concept called Detours. a Detour is when the memory location for a specific method on a specific instance is updated to point to a mock class with the same method signature. In other words this gives us a back door to wiring in mock functionality. Below is a brief example of how Moles works but a full explanation  of Moles is out of scope for this post so if you are new to Moles head on over to MSR and check out their demos and How-Tos.

y2kbug

 

Moles and Workflow Activities

Below is a diagram showing the basic architecture of how I’ve chosen to unit test activities. Starting from the unit test, Moles and a Test Stand-In classes are used to manipulate and confine the system under test (the Workflow Activity). Moles is used to wire in delegates that allows us to eliminate pieces of typical Activity execution that are not necessary parts of our test such as getting values from a InArgument which normally incorporates complex WF infrastructure. The test Stand-In is a simple component that replaces production classes with simplified test versions that provide test hooks and very predictable behavior. You’ll also see the use of test shims that expose protected members or my activities. I’ve chosen to use a derived class which uses the new keyword to create a public version of Execute which in turn calls the base version of Execute. You can use shadowing Accessors provided by the unit test wizards but I prefer the more explicit form of a derived class for the sake of a demo. Below is a diagram that shows the architecture of these unit tests and how a combinations of Test Stan-In classes and Moles allows us to focus the tests to a minimal amount of code.

image

Let’s dig into a Workflow specific use of Moles. Below are a few code samples of unit tests using Moles. You can find a link at the bottom of this post to the complete Visual Studio 2010 solution if you want more details about implementation.

Testing Execute on SimpleActivity

In this example we are testing SimpleActivity.Execute(CodeActivityContext context).  As part of Execute a CustomTrackingRecord is tracked and the same integer that is passed in to the InArgument<int> SomeInteger is returned. Both of these operations require the CodeActivityContext which we will need Moles to instantiate as Moles has the ability to create instances of classes even when their constructors are marked internal. Below is the code snippet for the entire unit test. After which each section is discussed in detail.

         /// <summary>
        ///A test for Execute
        ///</summary>
        [TestMethod()]
        [DeploymentItem("SampleActivities.dll")]
        [HostType("Moles")]
        public void ShouldReturnInt_WhenExecute_GivenInteger()
        {
            // Arrange
            int expected = 10;

            /// we need to create a moled CodeActivityContext so we can 
            /// get an instance from the Instance property of the moled 
            /// context as all constructors are marked as internal.
            MCodeActivityContext moledContext = new MCodeActivityContext();
            moledContext.TrackCustomTrackingRecord = (record) =>
            {
                // do nothing
            };
            /// We need to create a moled InArgument<int> so we can setup
            /// a delegate to return our expected value without using 
            /// relying on any of the complexities of the WF runtime.
            MInArgument01<int> moledInArg = new MInArgument01<int>();
            moledInArg.GetActivityContext = (ContextBoundObject =>
                {
                    return expected;
                });

            TestableSimpleActivity target = new TestableSimpleActivity();
            target.SomeInteger = moledInArg.Instance;

            // Act
            int actual = target.Execute(moledContext.Instance);

            //Assert
            Assert.AreEqual<int>(expected, actual);

        }

 

In the // Arrange section of the unit test two moles are created to handle various operations that we can’t create test Stand-Ins for. First we create a mole for the CodeActivityContext using MCodeActivityContext . Then we wire in the delegate for MCodeActivityContext .Track to MCodeActivityContext .TrackCustomTrackingRecord . This is the delegate that will serve as the Detour for the Track method. In other words this is the code that will be executed when Track is called on CodeActivityContext. In this detour delegate we don’t do any asserts but we could verify that the track call is tracking the correct data.

Next we wire in a delegate for InArgument<int>.Get. We can instantiate an InArgument but the Get call requires additional WF infrastructure that can’t reasonably reproduced outside of the WF Runtime so we detour it to get the simple behavior we want which is to just return the value expected.

Once our detours are setup we create an instance of the system under test using the Test Shim TestableSimpleActivity which, as discussed before, is simply a class that exposes protected members. Once we have our target object I set the SomeInteger property using the Instance property on our Mole object. We have to use the instance of InArgument<int> provided by the moled object as this is the instance that will be associated with our detours. If I just created and used another instance of InArgument<int> our detours would be ignored.

After we have the target object setup Execute is called using the instance of CodeActivityContext that is associated with our detours provided by the mole mole object moledContext.

Testing Asynchronous Execute on ComplexActivity

In the previous example I showed how to unit test the Execute method of a basic CodeActivity. However the situation is rarely that simple in the real world. To showcase unit testing more complex activities I’ve created an activity that uses asynchronous Workflow concepts. There are several ways to create asynchronous activities. I’ve chosen to start with NativeActivity as this allows me to cover the use of bookmarks. In this unit test we are testing Execute again but this time it is an asynchronous operation and it creates a new thread using System.Task so there are a few extra steps. For example an external service is responsible for doing the actual work.

         [TestMethod]
        [HostType("Moles")]
        public void ShouldDoWork_WhenExecute_Moled()
        {
            //Arrange
            //Create test fixture version of target type to expose protected members.
            TestableComplexActivity target = new TestableComplexActivity();
            //Create a stand-in version of IWorkerService with my test hooks.
            TestWorkerService testSvc = new TestWorkerService();
            BookmarkOptions actualBmOptions = BookmarkOptions.None; 
            //Create a moled NativeActivityContext to get access to an instance.
            //And to set a Detour for CreateBookmark
            MNativeActivityContext moledContext = new MNativeActivityContext();
            moledContext.CreateBookmarkStringBookmarkCallbackBookmarkOptions
              = (bmName, bmCallback, bmOptions) =>
            {
                actualBmOptions = bmOptions;
                return new Bookmark(bmName);
            };

            //Create a moled ActivityContext to setup a detour for 
            //GetExtention<T>(Type)
            MActivityContext moledActContext = 
            new MActivityContext(moledContext.Instance);
            moledActContext.GetExtension<IWorkerService>(
              new MolesDelegates.Func<IWorkerService>(() => testSvc)
              );

            bool actual = false;



            //Act
            target.Execute(moledContext);
            //blocks until worker thread completes.
            actual = testSvc.WaitForWorkToComplete();

            //Assert
            Assert.AreEqual<bool>(true, actual);
            Assert.AreEqual<BookmarkOptions>(BookmarkOptions.MultipleResume,
            actualBmOptions);
        }

To help constrain the test to just the Execute method I’ve created a test Stand-In version of the IWorkerService extension service called TestWorkerService to synchronize the threads for me and give me access to the data being passed into the service by Execute. This is an example of leveraging interfaces for the sake of testability via Stand-Ins. Another aspect that is new to this scenario is working with bookmarks. Bookmarks are a key component to asynchronous activities and are used to indicate where the workflow is suppose to resume execution upon completion of some external process. To accommodate this in our unit test we have to setup a Moles delegate on the Moles version of NativeActivityContext.CreateBookmark method. I use the Moles delegate to capture the BookmarkOptions value so I can validate that against an expected value using an Assert later in the unit test. Another addition to this scenario is providing a MolesDelegates.Func to handle access to the test extension service when the Execute method calls GetExtension<IWorkerService>(). Finally the last piece that is new to this scenario is the synchronization.

In the //Act section of the unit test you will notice a call to WaitForWorkToComplete on the instance of TestWorkerService. This call blocks until a WaitHandle is signaled by the new thread spawned by Execute and returns the value passed into the worker services' DoWork method. Since the behavior of the “production” worker service is simply to return the value passed in to the DoWork method the test Stand-In emulates the same behavior.

Sample Solution

The sample solution contains a few more unit test scenarios not covered here.  Below is a list of additional scenarios contained in the sample project.

  • A test for the Abort method
  • A test for removing MultipleResume bookmarks within bookmark callback methods
  • A test for not removing MultipleResume bookmarks  when asynchronous process fails.
  • A test that verifies tracking data.

 

Complete sample code solution (VS 2010)UnitTestingActivitiesWithMoles.zip

Comments

  • Anonymous
    March 21, 2011
    I would rather use ron jacobs workflow testing library. Let's you easily test bookmarks etc. Daniel

  • Anonymous
    March 22, 2011
    Thanks for the tip. I hadn’t seen Ron’s stuff until you mentioned it. I’ll have to give it a try. And while I've never used Ron's stuff, from looking at the source code it doesn't look like true unit testing as the WorkflowApplicationTest is still executing the workflow within the workflow runtime using System.Activities.WorkflowApplication. Meaning tests using this approach are technically more like an integration test. I’ve done plenty of WF testing using a similar approach with good results so it’s probably more of a style / preference decision.

  • Anonymous
    March 22, 2011
    The comment has been removed

  • Anonymous
    March 24, 2011
    The comment has been removed

  • Anonymous
    March 24, 2011
    Nice point. I definitely see your point about speed and performance. Although for simple tests we used the WorkflowInvoker to host the activity. This went really smooth. If you want and can give you the timing behavior of all our activity related tests... Daniel

  • Anonymous
    May 11, 2012
    The comment has been removed