How to Unit Test a WorkflowService

Testing WorkflowServices is harder because you have to host the service somehow.  There are several options you can use.

  1. You can host the service in IIS/AppFabric by configuring your project to use IIS
  2. You can try to insure that the local ASP.NET Development Server is started before testing (See my post on the Canonical REST Entity Service for an example).
  3. You can self-host the service

Unless your solution forces you to option 1 or 2 for some reason I recommend self-hosting for unit testing of services.  The best solution is to use named pipes for your protocol because this avoids issues with URL ACLs for HTTP and Firewalls for TCP.

In this post I’m going to show you how I tested an example WorkflowService that illustrates how you can use the AddDefaultExtensionProvider method to insure that an extension exists when your activity needs it.

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

For a simple example I created an activity that stores an integer and then returns a string that reports the number that  was stored into the extension and the average of the collection.  Here is a screenshot of the UI with the string returned from the activity.

image

When you have an activity that requires an extension you can provide a default mechanism for creating the extension if it does not already exist in the extensions collection by overriding Activity.CacheMetadata

 public sealed class GetAverage : CodeActivity<string>

{

    public InArgument<int> Number { get; set; }





    protected override void CacheMetadata(CodeActivityMetadata metadata)

    {

        // This activity requires the average extension

        metadata.RequireExtension(typeof(AverageExtension));



        // This lambda will create one if it is not already added

        metadata.AddDefaultExtensionProvider(() => new AverageExtension());



        base.CacheMetadata(metadata);

    }



    protected override string Execute(CodeActivityContext context)

    {

        // Get the average extension

        var average = context.GetExtension<AverageExtension>() as AverageExtension;



        if (average == null)

            throw new InvalidOperationException("Cannot access AverageExtension");



        var number = Number.Get(context);



        // Store this number

        average.StoreNumber(number);



        return string.Format("Stored {0}, Average:{1}", number, average.GetAverage());

    }

}

Testing The Workflow Service

Once again, using WorkflowTestHelper makes it easy to test a WorkflowService.  Step by step, here is how you do it.

  1. Add a reference to the binaries from WorkflowTestHelper to your test project
  2. Enable Deployment for VS Unit Tests
  3. Add the DeploymentItem attribute for the WorkflowService.  The path is relative to the path of the solution (.sln) file
  4. Use the WorkflowServiceTestHost class to host the test

In the code below I’m using the ServiceClient proxy class from the web project to communicate with the workflow service

 private readonly Binding _binding = new NetNamedPipeBinding();



/// <summary>

///   The endpoint address to be used by the test host

/// </summary>

private readonly EndpointAddress _serviceAddress = new EndpointAddress("net.pipe://localhost/TestService");





/// <summary>

/// Tests the GetAverage Activity by invoking it in a WorkflowService multiple times

/// </summary>

[TestMethod()]

[DeploymentItem("Service1.xamlx")]

public void ShouldInvokeAverageExtension()

{

    const string expected1 = "Stored 33, Average:33";

    const string expected2 = "Stored 44, Average:38.5";

    const string expected3 = "Stored 55, Average:44";



    string result1;

    string result2;

    string result3;



    // Self-Host Service1.xamlx using Named Pipes

    using (var testHost = WorkflowServiceTestHost.Open("Service1.xamlx", _serviceAddress.Uri))

    {

        // Use the generated proxy with named pipes 

        var proxy = new ServiceClient(_binding, _serviceAddress);



        try

        {

            result1 = proxy.GetData(33);

            result2 = proxy.GetData(44);

            result3 = proxy.GetData(55);

            proxy.Close();

        }

        catch (Exception)

        {

            proxy.Abort();            

            throw;

        }



        Assert.AreEqual(expected1, result1);

        Assert.AreEqual(expected2, result2);

        Assert.AreEqual(expected3, result3);

    }

}

So Get Busy Testing

I’m always surprised by how many people do little or no testing of their code.  You can probably tell that I’m a big fan of testing and going even further I’m a proponent of Test Driven Development (or Test-First if you prefer).  When I started working with WF4 I realized how difficult this can be.  We still have a way to go to make it even better and I’ve got some more surprises in store for you so stay tuned…