locked
Unit Testing a Workflow Activity RRS feed

  • Question

  • I'm trying to unit test a workflow activity.  Here's the crux of the technique I am using:

     

    Code Snippet
    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 );
    
                    // -------------------------
                    // Comment out the following tow lines to make the test work
                    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"] );
            }
        }
    }
    
    
     

     

    This unit test fails. The problem occurs when using the SqlTrackingService.  If I comment out the lines where the tracking service is added, the unit test succeeds.

     

    The trace log is showing this exception:

     

    Code Snippet
    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)
    

     

     

    So my question is, is this a bug or is this a normal operation of the workflow runtime?

     

    If this is normal, what is the best work around for unit testing? Should I wrap my activity in a TransactionScopeActivity? If so, what then is the best way to get to the properties of the activity I am trying to unit test (since the OutputParameters value on the WorkflowCompletedEventArgs object would be for the TransactionScopeActivity object, not the activity I'm trying to unit test)?

     

    Thanks,

    -Dave

     

     

    Thursday, November 13, 2008 5:07 PM

Answers

  • Deriving my workflow acivity class from CompositeActivity rather than just Activity seems to fix the problem.

     

    Code Snippet
        public class TestActivity : CompositeActivity
        {
            ...
        }
    

     

     

    -Dave

    Thursday, November 13, 2008 5:13 PM

All replies

  • Deriving my workflow acivity class from CompositeActivity rather than just Activity seems to fix the problem.

     

    Code Snippet
        public class TestActivity : CompositeActivity
        {
            ...
        }
    

     

     

    -Dave

    Thursday, November 13, 2008 5:13 PM
  • My guess is that it is a bug. A suggestion rather than having to change your activity base class is to wrap your activity inside a sequential activity within your unit test via a workflowchanges snippet. Also, I'd suggest creating something like I did, a WorkflowExecutor class designed to run a specific activity to reuse all the code around creating the runtime, listening for events, the WaitOne logic, etc. NET 4.0 is adding something like this and it would reduce the amount of code you need in your unit test. You could put the activity wrapping in this class as well.
    Thursday, November 13, 2008 7:21 PM
  • In the actual test setup that I am using, the [TestInitialize] and [TestCleanup] are in a shared base class, so I do reuse the code.

     

    If you wrap an activity in another activity (for example, I tried the TransactionScopeActivity with success), then how do you get to the OutputParameters from the activity you are trying to unit test?  By wrapping the activity, the OutputParameters will be from the wrapping activity, not the activity you are trying to unit test.

     

    In other words, by wrapping the activity the following test would fail:

    Code Snippet
       Assert.AreEqual( "Success", (String)outputArgs["Message"] );
     

     

    Thanks for the thoughts,

    -Dave Baskin

    Thursday, November 13, 2008 7:47 PM