Thursday, November 29, 2012

Unit Testing Long Running Workflows by Mocking the TimerExtension

Problem

I've been teaching myself more about .Net Workflow Foundation (WF).  One thing I've been working on recently is how to unit test long running workflows.  I have a few examples of workflows that delay for days.  I'd love to be able to advance the clock and simulate what will happen when time advances past the delay without the wait.  There's a number of techniques out there that I have found.  They fall into one of several camps.
  • Roll your own Delay Activity
  • Use the custom Delay Activity from Microsoft.Activities.UnitTesting
  • Runtime XAMLInjection of modified Delay Timespan

None of these appeal to me.  I prefer the stock Delay Activity -- other than unit testing I have no real reason for modified behavior.  Runtime injection seems a bit of a kludge. I prefer to leave the logic of the workflow intact and only modify the dependencies from the outside.  In short, I have the same concern that is raised in this StackOverflow question about replacing the DurableTimerExtension.

I have the same interest as the original poster of this question.  How do I inject my own mocked variation of DateTime.Now()?  There are simlar, non-workflow implementations for Mocking DateTime.Now() and UnitTesting DateTime.Now(), just not a best practice for WF.

Here is one way to do it.

[Disclamer: The code I have below uses the Microsoft.Activities.UnitTesting extensions.  This technique wouldn't require that framework -- but its handy enough.  So I use it.]

Approach

On instances of WorkFlowApplication and WorkflowServiceHost there is an Extensions collection.  In WF, this is way to register workflow extensions like Tracking, PersistenceParticipants, and services that your custom Activities can consume.  One of the methods on this collection is Add().  which accepts a Func.  This is a function that will return type T specified when that type is requested via context.GetExtension() inside the execute method of an activity.  I've used this in the code below to inject my own Mock of the TimerExtension used by the Delay activity.  I've done this with an actual class -- but with a little imagination this can be modified to use a mock in your favorite mocking framework and extended to suit your interests.

Code @ Pastebin




Conclusion

This isn't quite the ideal in my mind. I'm intercepting the call that the Delay Activity (and anything else that wants to use the TimerExtension) makes to schedule a timer to fire and modifying the actual timespan that is used to schedule the timer.  Any workflows that attempt to reconcile time inside and outside of the workflow will have side effects... but, well, that's kinda the point.

I'd much rather intercept a request to an imaginary service from the workflow for "What time is it now?".  A default (and production implementation would reply "Why its DateTime.Now(),  of course [or is that UTCNow()..],  A mocked implementation would reply "Its DateTime.Now().Add(new Timespan(0,15,0)) [15 minutes in the future for the less initiated].

All of these techniques have their warts.  In any case, what I have written up is the closest to satifying my needs -- so I decided to write up a post for the benefit of all -- including future me.  I'd love to hear of other successful approaches you might have taken in the comments.


No comments: