Second Stanza

December 27, 2008

Unit Testing Workflow Activities

Filed under: .NET Development, WorkFlow — Tags: , — dfbaskin @ 10:30 pm

I’ve been using a technique similar to the code below to unit test custom workflow activities.


using System;
using System.Collections.Generic;
using System.Workflow.Runtime;
using System.Workflow.ComponentModel;
using System.Workflow.Runtime.Hosting;
using System.Workflow.Runtime.Tracking;
using System.Configuration;
using System.Threading;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace TrackingError
{
    public class TestActivity : Activity
    {
        public static readonly DependencyProperty MessageProperty = DependencyProperty.Register( "Message", typeof( String ), typeof( TestActivity ) );

        public string Message
        {
            get { return (String)GetValue( MessageProperty ); }
            set { SetValue( MessageProperty, value ); }
        }

        protected override ActivityExecutionStatus Execute( ActivityExecutionContext executionContext )
        {
            Message = "Success";
            return ActivityExecutionStatus.Closed;
        }
    }

    [TestClass]
    public class ShowTrackingError
    {
        public ShowTrackingError()
        {
        }

        private TestContext testContextInstance;

        public TestContext TestContext
        {
            get { return testContextInstance; }
            set { testContextInstance = value; }
        }

        private static string ConnectionString
        {
            get { return ConfigurationManager.ConnectionStrings["WorkFlowDB"].ConnectionString; }
        }

        private WorkflowRuntime workflowRuntime;
        private AutoResetEvent resetEvent;
        private Dictionary<String, Object> outputArgs;
        private bool completedWorkflow;

        [TestInitialize]
        public void CreateWorkFlowEnvironment()
        {
            bool startedWorkflow = false;
            try
            {
                resetEvent = new AutoResetEvent( false );
                outputArgs = null;
                completedWorkflow = false;

                workflowRuntime = new WorkflowRuntime();

                SharedConnectionWorkflowCommitWorkBatchService sharedConnService = new SharedConnectionWorkflowCommitWorkBatchService( ConnectionString );
                workflowRuntime.AddService( sharedConnService );

                SqlWorkflowPersistenceService persistService = new SqlWorkflowPersistenceService( ConnectionString );
                workflowRuntime.AddService( persistService );

                SqlTrackingService trackingService = new SqlTrackingService( ConnectionString );
                workflowRuntime.AddService( trackingService );

                workflowRuntime.WorkflowCompleted += delegate( object sender, WorkflowCompletedEventArgs e ) {
                    outputArgs = e.OutputParameters;
                    completedWorkflow = true;
                    resetEvent.Set();
                };
                workflowRuntime.WorkflowTerminated += delegate( object sender, WorkflowTerminatedEventArgs e ) {
                    resetEvent.Set();
                };
                workflowRuntime.WorkflowAborted += delegate( object sender, WorkflowEventArgs e ) {
                    resetEvent.Set();
                };

                workflowRuntime.StartRuntime();
                startedWorkflow = true;
            }
            finally
            {
                if( !startedWorkflow )
                    if( workflowRuntime != null )
                    {
                        workflowRuntime.Dispose();
                        workflowRuntime = null;
                    }
            }
        }

        [TestCleanup]
        public void ShutDownWorkFlowEnvironment()
        {
            if( workflowRuntime != null )
            {
                try
                {
                    workflowRuntime.StopRuntime();
                    workflowRuntime.Dispose();
                }
                finally
                {
                    workflowRuntime = null;
                }
            }
        }

        [TestMethod]
        public void UnitTestWorkflowActivity()
        {
            Assert.IsNotNull( workflowRuntime );

            WorkflowInstance wfInst = workflowRuntime.CreateWorkflow( typeof( TestActivity ) );
            Assert.IsNotNull( wfInst );

            wfInst.Start();

            resetEvent.WaitOne( 5000 );

            Assert.IsTrue( completedWorkflow );
            Assert.IsNotNull( outputArgs );
            Assert.AreEqual( "Success", (String)outputArgs["Message"] );
        }
    }
}

(Note that common code like the methods marked with the TestInitialize and TestCleanup attributes are actually in a shared based class, but included in the test class here for our discussion.)

However, the above unit test does not work. The activity is aborted so the completedWorkflow value is never set to true. This behavior is apparent both in this unit test as well as in an application that runs the single activity. Perhaps I’ve misunderstood something, but I believe this should work. The activity should not abort.

It appears that the SqlTrackingService class expects a CompositeActivity, not a Activity-derived class since the error written to the log is:

System.Workflow.Runtime.Hosting Error: 0 : SharedConnectionWorkflowCommitWorkBatchService caught
      exception from commitWorkBatchCallback: System.InvalidCastException: Unable to cast object
      of type 'TrackingError.TestActivity' to type 'System.Workflow.ComponentModel.CompositeActivity'.
   at System.Workflow.Runtime.Tracking.SqlTrackingService.SqlTrackingChannel.InsertWorkflow(DbCommand command, Guid workflowInstanceId, Type workflowType, Activity rootActivity)
   at System.Workflow.Runtime.Tracking.SqlTrackingService.SqlTrackingChannel.ExecuteInsertWorkflowInstance(DbCommand command)
   at System.Workflow.Runtime.Tracking.SqlTrackingService.SqlTrackingChannel.Commit(Transaction transaction, ICollection items)
   at System.Workflow.Runtime.WorkBatch.PendingWorkCollection.Commit(Transaction transaction)
   at System.Workflow.Runtime.WorkBatch.Commit(Transaction transaction)
   at System.Workflow.Runtime.VolatileResourceManager.Commit()
   at System.Workflow.Runtime.WorkflowExecutor.DoResourceManagerCommit()
   at System.Workflow.Runtime.Hosting.SharedConnectionWorkflowCommitWorkBatchService.CommitWorkBatch(CommitWorkBatchCallback commitWorkBatchCallback)

Several work-arounds make this unit test pass:

  • Instead of derving the custom activity from the Activity class, it can be derived from CompositeActivity.
  • The activity can be wrapped in some other activity, such as the TransactionScopeActivity. However, the properties used to test the results of the workflow must be propagated out to the wrapping activity.
  • The SqlTrackingService workflow runtime service can be removed.

I’ve opened a Microsoft Connect feedback item here. A Microsoft forums post is here.

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Blog at WordPress.com.

%d bloggers like this: