Archive

Posts Tagged ‘Rhino Mocks’

Unit Testing custom Workflow Activities

January 8th, 2008 stiiifff No comments

I recently had to create some custom activities that had to receive external data from or send data to external services. As I wanted to be sure that the behavior of my custom activities was correct, I tried to write some unit tests for them. As I found out, there aren’t much articles yet about unit testing such custom activities … here the 2 interesting ones I found:

Hope I can add my 2 cents on the subject with this article. :)

Let’s start by describing how my custom activity look like : I will skip the simple cases in which the activity is not communicating with the outside world (not receiving events, not calling methods) … in order to test such activity, we just need to check the end result of the workflow (output) given an input.

My custom will be slightly more complex as it will be able to receive events and / or call methods using WF’s HandleExternalEventActivity & CallExternalMethodActivity built-in activities. Yet, for simpleness of the example, I will not include any FaultHandler activities or more generally, error-handling code.

Ok, now let’s dig in :)

My activity will use an external service defined by the following interface:

[ExternalDataExchange]
public interface ICustomService
{
    event EventHandler MyEvent;
    string MyMethod(string input);
}

[Serializable]
public class CustomServiceEventArgs : ExternalDataEventArgs
{
    private readonly string input;

    public CustomServiceEventArgs(Guid instanceId, string input) : base(instanceId)
    {
        this.input = input;
    }

    public string Input
    {
        get { return input; }
    }
}

This dumb service contains a simple event and a simple method.
And here is a dumb implementation of this service:

public class CustomService : ICustomService
{
    public event EventHandler<CustomServiceEventArgs> MyEvent;

    public string MyMethod(string input)
    {
        return input.ToUpperInvariant();
    }

    public void RaiseMyEvent(Guid instanceId, string input)
    {
    if (MyEvent != null)
        MyEvent(null, new CustomServiceEventArgs(instanceId, input));
    }
}

The implementation of the MyMethod method is just ‘upper-casing’ the input string.

Now, I build a custom sequence activity (inherited from SequenceActivity) that do 2 things:

  • Wait for the event MyEvent to be triggered on ICustomService service.
  • When the event is triggered, keep the value of the Input property of the received CustomServiceEventArgs object in a property of the custom activity.
  • Call the MyMethod method of the ICustomerService, passing in the value of the Input property.

(For brievity purpose, I don’t show the code / screenshots of this custom activity, you can have a look at it in the source code provided at the end of this article).

Now, let’s say I build a simple Sequential workflow containing only my custom activity, and I would like to write a unit test verifying that my custom activity is working correctly.

The first approach is to test the output of the workflow, based on a pre-defined input … a test method to do that could look more or less like this (using NUnit):

// This test just verify that the output of the workflow is as expected.
// Some problems with this approach:
//  - The test is dependent on the ICustomService service's implementaion class (CustomService).
//  - Ok for simple custom activities ... maybe less reliable for complex event-based custom activities.
[Test]
public void SimpleTest()
{
    using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())
    {
        ExternalDataExchangeService dataExchange = new ExternalDataExchangeService();
        workflowRuntime.AddService(dataExchange);

        // Create instance of ICustomService using concret implementation class CustomService
        CustomService customService = new CustomService();
        dataExchange.AddService(customService);

        string workflowOutput = null;

        AutoResetEvent waitHandle = new AutoResetEvent(false);
        workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e)
        {
            workflowOutput = e.OutputParameters["WorkflowOutput"] as string;
            waitHandle.Set();
        };
        workflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e)
        {
            Console.WriteLine(e.Exception.Message);
            waitHandle.Set();
        };

        WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(TestWorkflow));
        instance.Start();

        // Raise event using helper method in implementation class
        customService.RaiseMyEvent(instance.InstanceId, "inputdata");

        waitHandle.WaitOne();

        // Check that output is as expected
        Assert.AreEqual("INPUTDATA", workflowOutput);
    }
}

As you can read it in the comment of the test method, this unit test has some limitations:

  • The test is dependent on the existing ICustomService service’s implementaion class (CustomService).
  • It might be ok for simple custom activities … but it will most probably be less reliable for complex event-based custom activities.

A better way IMHO might be to use a mock object for the ICustomerService service, and define expectations on it. This approach is show here (using NUnit / RhinoMocks):

// This test  verify that the output of the workflow is as expected as well as the 'behavior'
// (subscription to external events, calls to external methods, and in which order they occur)
// Some advantages of this approach:
//  - The test is independent on the ICustomService service's implementation class (CustomService)
//        -> the CustomService class can be tested separately with another test case.
//  - Deeper testing of the actual behaviour of the custom activity regarding external inputs / outputs (events / methods)
[Test]
public void AdvancedTest()
{
    using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())
    {
        MockRepository mockRepository = new MockRepository();

        // Create mock object for service ICustomerService
        ICustomService mockCustomService = mockRepository.CreateMock<ICustomService>();

        // We need an 'event raiser' for the 'MyEvent' event of the ICustomService
        IEventRaiser raiseMyEvent = null;

        // Declare that the expectations have to occur in the specified order
        using (mockRepository.Ordered())
        {
            // Declare expectations on mock service
            mockCustomService.MyEvent += null; // Create an expectation that someone will subscribe to this event
            LastCall.IgnoreArguments(); // we don't care who is subscribing
            raiseMyEvent = LastCall.GetEventRaiser(); // Get event raiser for the last event, in this case, MyEvent

            // Create an expectation that someone will call MyMethod with "inputdata" as parameter,
            // and instruct mock object to return "INPUTDATA" in that case
            Expect.Call(
                mockCustomService.MyMethod("inputdata")
            ).Return("INPUTDATA");
        }
        mockRepository.ReplayAll();    // Expectations declared, we are ready to start the test

        ExternalDataExchangeService dataExchange = new ExternalDataExchangeService();
        workflowRuntime.AddService(dataExchange);
        dataExchange.AddService(mockCustomService);

        string workflowOutput = null;

        AutoResetEvent waitHandle = new AutoResetEvent(false);
        workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e)
        {
            workflowOutput = e.OutputParameters["WorkflowOutput"] as string;
            waitHandle.Set();
        };
        workflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e)
        {
            Console.WriteLine(e.Exception.Message);
            waitHandle.Set();
        };

        WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(TestWorkflow));
        instance.Start();

        // Raise the 'MyEvent' event of the mocked service
        raiseMyEvent.Raise(null, new CustomServiceEventArgs(instance.InstanceId, "inputdata"));

        waitHandle.WaitOne();

        // Check output data AND expectations
        Assert.AreEqual("INPUTDATA", workflowOutput);
        mockRepository.VerifyAll();
    }
}

Advantages of using this approach :

  • The test is independent on the ICustomService service’s implementation class (CustomService) -> the CustomService class can be tested separately with another test case (which is actually recommended).
  • Deeper testing of the actual behavior of the custom activity regarding external inputs / outputs (events / methods) … some might say that the test is then more tightly coupled with the implementation of the custom activity … this can be debated. :)

IMHO, this second approach allows to build unit tests that are much more reliable when you want to make sure you can detect any regression in the output but also in the behaviors of your custom activities.

I will soon post a link pointing to the sample workflow test project with the full source code illustrating this article.

Not all aspects of unit testing workflows were treated here but at least an interesting one that, I hope, will help you create better& smarter custom activities! :)

Categories: TDD, WF Tags: , , , ,