Why Does my DelayActivity's InitializeTimeoutDuration not work?
The other day I was writing some unit tests for a StateMachine workflow. My StateMachine had a state where it uses a ReceiveActivity to receive a web service message. In the same state it has an event that uses a DelayActiviity to handle a timeout for cases where the web service message was not sent in a timely manner.
I wanted to test my StateMachine but I didn't want the unit test to have to wait for the timeout. Ideally your unit tests should run very quickly and testing timeouts is problematic. So here is what I did.
I added some code to read the timeout value from a config file. Then I added a handler for the DelayActivity InitializeTimeoutDuration event. My demo code normally waits for 10 seconds but in my unit test I wanted to wait for just 100 milliseconds.
I then wrote my test to start the workflow and then wait for the timeout to occur. When it does my workflow was set to suspend so all I had to do was to handle the WorkflowRuntime.WorkflowSuspended event to know if it did suspend.
But then every time I ran my unit test the test would wait for 10 seconds and fail. I verified that my code was indeed reading the correct value (100 milliseconds) from the config file and setting the timeout duration like this
private void OnInitializeTimeout(object sender, EventArgs e)
{
this.delayForScoreTimeout.TimeoutDuration = this.Loan.LoanTimeout.Value;
}
I searched the web for help on this and ran across an answer in our MSDN forum from Tom Lake (one of the testers on the workflow team). The problem is caused by spawned contexts.
Spawned what?
Yes - when a state is entered in the state machine (or when other workflow activities like the ReplicatorActivity, WhileActivity or ConditionedActivityGroup are executed) the workflow engine makes a clone of activities from your workflow definition and execute them instead of the activities in your workflow definition. For this reason you can't just change the activity as I did above but you have to change the instance that the workflow is actually executing.
How do I change the correct instance?
Take advantage of the sender argument like this
private void OnInitializeTimeout(object sender, EventArgs e)
{
(sender as DelayActivity).TimeoutDuration = this.Loan.LoanTimeout.Value;
}
The sender will be the instance of the DelayActivity that the workflfow is actually executing. Whew! Got it working. Thanks Tom!
Comments
Anonymous
July 31, 2008
PingBack from http://www.basketballs-sports.info/basketball-chat/?p=1972Anonymous
July 31, 2008
Wouldn't it be great if you there was separation between how long you wanted to wait and the actual timeout trigger? That way you wouldn't need to look at ways to change the production timeout value, or make it configurable or anything. Actually waiting any period of time in your unit tests can severely slow down your continuous integration: if you have a lot of long-running business logic, and many tests to cover it, that can mean long-minutes of waiting before checking in any changes to a workflow. In nServiceBus, you'd just call the Timeout method and be done with it. No waiting necessary. Is there any way to not wait in your test?Anonymous
August 21, 2008
My test had to verify the behavior of the workflow in a timeout scenario. I could have reached in to the workflow and artificially invoked the timeout state but that would have manipulated the environment in a way that is not true to production. Instead I wanted to shorten the timeout that my unit test was using. I agree waiting in unit tests is bad so I made the wait really short (100ms).