In my previous post I showed you how to test simple activities using WorkflowInvoker.  While this is a great way to test simple activities it does have limitations.  The MSDN documentation states

WorkflowInvoker does not allow instance control such as persisting, unloading, or resuming bookmarks. If instance control is desired, use WorkflowApplication instead.

In this post I’ll show you how you can use WorkflowTestHelper to test an activity that uses Bookmarks.

Watch endpoint.tv - How To Unit Test Workflows
Download Workflow Test Helper (including this sample code) from MSDN Code Gallery

What is a Bookmark?

Suppose you have an activity that needs some data.  Perhaps you want to ask the user for something or you need the host environment to do something that the activity itself cannot do.  In this example I’m going to create a ReadLine activity.  I could call Console.ReadLine from my activity but that is a bad practice because it blocks the Activity Scheduler thread.  As an activity author you should never block the thread but instead should use bookmarks.

Here is my ReadLine Activity

public sealed class ReadLine : NativeActivity<string>

{
private BookmarkCallback _readCompleteCallback;

[RequiredArgument]
public InArgument<string> BookmarkName { get; set; }

protected override bool CanInduceIdle
{
get { return true; }
}

public BookmarkCallback ReadCompleteCallback
{
get { return _readCompleteCallback ?? (_readCompleteCallback = new BookmarkCallback(OnReadComplete)); }
}

protected override void Execute(NativeActivityContext context)
{
// Inform the host that this activity needs data and wait for the callback
context.CreateBookmark(BookmarkName.Get(context), ReadCompleteCallback);
}

private void OnReadComplete(NativeActivityContext context, Bookmark bookmark, object state)
{
// Store the value returned by the host
context.SetValue(Result, state as string);
}
}

Now I can use this activity in a workflow that requires input from the user

SNAGHTMLb967e9

If you try to run this workflow with WorkflowInvoker your app will hang.  Instead you have to use WorkflowApplication and deal with the bookmarks in the host application.  I’ve included the host application with this post so you can see the necessary code.

How To Test This Workflow

Testing an activity like this can be tricky.  In the WorkflowTestHelper library I’ve added a class that makes it much easier.  The class is called WorkflowApplicationTest<T>.  This class will create and manage the WorkflowApplication and handle all the events for you resulting in some very clean test code.

[TestMethod]

public void ShouldOutputGreeting()
{
// Arrange
const string expectedFirstName = "Test";
const string expectedLastName = "User";
var expectedGreeting = string.Format("Hello {0} {1}", expectedFirstName, expectedLastName);
var sut = WorkflowApplicationTest.Create(new TestReadLine());

// Act

// Run the workflow
sut.TestActivity();

// Wait for the first idle event - prompt for First Name
// will return false if the activity does not go idle within the
// timeout (default 1 sec)
Assert.IsTrue(sut.WaitForIdleEvent());

// Should have a bookmark named "FirstName"
Assert.IsTrue(sut.Bookmarks.Contains("FirstName"));

Assert.AreEqual(BookmarkResumptionResult.Success,
sut.TestWorkflowApplication.ResumeBookmark("FirstName", expectedFirstName));

// Wait for the second idle event - prompt for Last Name
Assert.IsTrue(sut.WaitForIdleEvent());

// Should have a bookmark named "LastName"
Assert.IsTrue(sut.Bookmarks.Contains("LastName"));

Assert.AreEqual(BookmarkResumptionResult.Success,
sut.TestWorkflowApplication.ResumeBookmark("LastName", expectedLastName));

// Wait for the workflow to complete
Assert.IsTrue(sut.WaitForCompletedEvent());

// Assert
// WorkflowApplicationTest.TextLines returns an array of strings
// that contains strings written by the WriteLine activity
Assert.AreEqual(4, sut.TextLines.Length);
Assert.AreEqual(expectedGreeting, sut.TextLines[2]);
}

Give It A Try

I’ve just posted WorkflowTestHelper v1.3 which now provides better support for testing with Bookmarks and capturing output of WriteLine activities.  Give it a try and let me know what you think.