Microsoft.Activities.UnitTesting

Microsoft.Activities.UnitTesting is a unit testing framework designed to make it easier to unit test activities and workflow services built with Windows Workflow Foundation (WF4).
The Framework contains
  • Test Hosts which wrap WorkflowInvoker, WorkflowApplication and WorkflowServiceHost
  • Assert helper classes for asserting out arguments and tracking records
  • Support for Xaml Injection which allows you to mock or stub activities that you don't want to run when testing
  • Test Framework independent - you can use it with MSTest, NUnit, xUnit or whatever test framework you like.

How do I...

How do I install Microsoft.Activities.UnitTesting?
How do I use Microsoft.Activities.UnitTesting?
How do I test an activity with bookmarks?
How do I test a Workflow Service?
How do I Mock Receive and Send activities?
How do I do Test Driven Development with Windows Workflow Foundation (WF4) and Microsoft.Activities.UnitTesting?
How do I mock activities when testing Workflow Services with XAML Injection?

Testing with a SQL Instance Store

What are some tips for unit testing with a database?
How do I unit test with a SQL Instance Store database?

Samples

Samples are installed with the NuGet package when you install-package Microsoft.Activities.UnitTesting. You will find them under the packages folder.
samplespackage.png

Open Specs

Test Activities

Activity Description
TestAsync An AsyncCodeActivity that will sleep for a specified duration
DiagnosticTrace Allows you to output a System.Diagnostics.Trace message as well as a custom tracking record
TestBookmark<T> Create a bookmark with a name and result type of T

Utility Classes

WorkflowInvokerTest - Helps you to write unit tests with simple activities while capturing tracking information and output arguments
[TestMethod]
public void ShouldAddGoodSumAssertOutArgument()
{
    var sut = new WorkflowInvokerTest(new GoodSum {x = 1, y = 2});
    sut.TestActivity();

    sut.AssertOutArgument.AreEqual("sum", 3);
}

WorkflowApplicationTest - Helps you to write unit tests using WorkflowApplication for workflows that are more complex requiring support for bookmarks. Also captures all event args and tracking information

[TestMethod]
public void ShouldTestActivityWithBookmarks()
{
    // Arrange
    var wfAppTest = WorkflowApplicationTest.Create(new ActivityWithBookmarks());

    // Act
    wfAppTest.TestActivity();

    // Assert
    Assert.IsTrue(wfAppTest.WaitForIdleEvent());

    Assert.IsTrue(wfAppTest.Bookmarks.Contains("GetNumber2"));
    Assert.AreEqual(BookmarkResumptionResult.Success,
                    wfAppTest.TestWorkflowApplication.ResumeBookmark("GetNumber2", 2));

    Assert.IsTrue(wfAppTest.WaitForIdleEvent());
    Assert.IsTrue(wfAppTest.Bookmarks.Contains("GetNumber3"));
    Assert.AreEqual(BookmarkResumptionResult.Success,
                    wfAppTest.TestWorkflowApplication.ResumeBookmark("GetNumber3", 3));

    Assert.IsTrue(wfAppTest.WaitForCompletedEvent());
    wfAppTest.AssertOutArgument.AreEqual("Number", 6);
}

AssertOutArgument - Allows you to assert that an out argument with the given name exists, is of the same type as the expected value and provides AreEqual, AreNotEqual, IsNull, IsNotNull etc. Exposed through WorkflowTestApplication class as a property
    // Assert
    wfAppTest.AssertOutArgument.AreEqual("Greeting", expectedGreeting);
    wfAppTest.AssertOutArgument.AreNotEqual("WorkflowThread", testThread);
    wfAppTest.AssertOutArgument.AreEqual("WorkflowThread", actionThread);

WorkflowTestResults - Captures all the event arguments provided from WorkflowApplication along with tracking records - access through the WorkflowTestApplication.Results property

AssertTracking - Asserts that tracking records exist after test run

WorkflowServiceTestHost - Hosts a WorkflowService (.xamlx) and captures tracking for testing
[TestMethod]
[DeploymentItem(@"WorkflowTestHelper.Tests.Activities\TestService.xamlx")]
public void ShouldHostService()
{
    var trackingProfile =
        new TrackingProfile
            {
                Queries =
                    {
                        new ActivityStateQuery
                            {
                                ActivityName = "ReceiveRequest",
                                States = {"Executing"},
                            },
                        new ActivityStateQuery
                            {
                                ActivityName = "SendResponse",
                                States = {"Executing"},
                            },
                    }
            };


    using (var host = WorkflowServiceTestHost.Open("TestService.xamlx", _serviceAddress.Uri, trackingProfile))
    {
        var client = ChannelFactory<ITestService>.CreateChannel(_binding, _serviceAddress);
        var response = client.GetData(1);
        Assert.AreEqual("1", response);

        host.Tracking.Trace();

        // Find the tracking records for the ReceiveRequest and SendResponse

        // Activity <ReceiveRequest> state is Executing
        AssertTracking.ExistsAt(host.Tracking.Records, 0, "ReceiveRequest", ActivityInstanceState.Executing);

        // Activity <SendResponse> state is Executing
        AssertTracking.ExistsAt(host.Tracking.Records, 1, "SendResponse", ActivityInstanceState.Executing);
    }
}

MemoryStore - A memory based PersistenceStore allows you to test workflows that become idle, persist and reload. WorkflowServiceTestHost automatically adds this as the persistence store.

/// <summary>
///   Demonstrates how to test a service with correlation
/// </summary>
/// <remarks>
///   Be sure to enable deployment - the xamlx file must be deployed
/// </remarks>
[TestMethod]
[DeploymentItem(@"WorkflowTestHelper.Tests.Activities\ServiceWithCorrelation.xamlx")]
public void ShouldCorrelateServiceCalls()
{
    using (var testHost = new WorkflowServiceTestHost("ServiceWithCorrelation.xamlx", _serviceAddress.Uri))
    {
        // Add an idle behavior to unload as soon as idle is detected
        testHost.Host.Description.Behaviors.Add(new WorkflowIdleBehavior
                                                    {TimeToUnload = TimeSpan.Zero});
        testHost.Open();

        var client = ChannelFactory<IServiceWithCorrelation>.CreateChannel(_binding, _serviceAddress);
        Trace.WriteLine("Test Client: Sending GetData(1)");
        var response = client.GetData(1);
        Assert.AreEqual("1", response.Text);

        Trace.WriteLine(string.Format("Test Client: Received GetData response {0} with key {1}", response.Text,
                                        response.Key));

        // Wait for unload
        Thread.Sleep(1000);

        // If you want to see what is in the memory store, dump it
        // MemoryStore.Dump();

        Trace.WriteLine(string.Format("Test Client:  Sending GetMoreData(2, {0})", response.Key));
        var secondResponse = client.GetMoreData(2, response.Key);
        Assert.AreEqual("2", secondResponse.Text);

        Trace.WriteLine(string.Format("Test Client: Received GetMoreData response {0} with key {1}", secondResponse.Text,
                                        secondResponse.Key));

        testHost.Tracking.Trace();

        MemoryStore.DisplayCommandCounts();
        Assert.AreEqual(1, MemoryStore.LoadWorkflowCommandCount);
        Assert.AreEqual(1, MemoryStore.LoadWorkflowByInstanceKeyCommandCount);
        Assert.AreEqual(3, MemoryStore.SaveWorkflowCommandCount);
        Assert.AreEqual(1, MemoryStore.CreateWorkflowOwnerCommandCount);
    }
}

XamlInjector - Allows you to substitute mock activities when testing Activities or WorkflowServices
[TestMethod]
[DeploymentItem(@"WorkflowTestHelper.Tests\TestInject.xaml")]
public void ShouldReplaceTypesInXaml()
{
    var xamlInjector = new XamlInjector("TestInject.xaml");

    // The first TestActivity1 will not be replaced - will add 1 to sum

    // Replace the second TestActivity1 with TestActivity2 - will add 2 to sum
    xamlInjector.ReplaceAt(1, typeof (TestActivity1), typeof (TestActivity2));

    // Replace third TestActivity1 with TestActivity3 - will add 3 to sum
    xamlInjector.ReplaceAt(2, typeof (TestActivity1), typeof (TestActivity3));

    // Replace all (2) TestActivity4 with TestActivity5 - will add 10 to sum
    xamlInjector.ReplaceAll(typeof (TestActivity4), typeof (TestActivity5));
    var activity = xamlInjector.GetActivity();

    Debug.WriteLine(string.Format("Invoking Injected XAML activity {0}", activity.GetType()));

    var wiTest = new WorkflowInvokerTest(activity);

    // Act
    wiTest.TestActivity();

    // Total should be 1+2+3+10=16
    wiTest.AssertOutArgument.AreEqual("sum", 16);
}

Last edited Jun 28, 2012 at 9:11 PM by ronjacobs, version 31

Comments

thxmike Apr 10, 2012 at 9:36 PM 
I figured this out on my own. The result of the execution of the activity adds to the output arguments a Argument Property called "Result".

thxmike Apr 10, 2012 at 7:59 PM 
Lets say I have a codeactivity that inherits from a codeactivity<boolean>.
i.e. SomeActivity : CodeActivity <bool>
If my host is the workflowinvoker and since this returns the execution result to the workflowinvoker.invoke method to true or false, how would I achieve the same results for the WorkFlowInvokerTest?