Mocking a timer
Most of us in the testing world are probably familiar with the following problem:
We want to test, that once we call a method, it will start a timer which will then invoke another method when the interval elapsed.
A very simple test, that just deals with the System Timer itself could look like this:
Although we told it to fire after only 1ms, this test will fail (at least most times) because thevar mockTimer = new System.Timers.Timer(1);
bool elapsedFired = false;
mockTimer.Elapsed += (sender, e) => elapsedFired = true;
mockTimer.Start();
Assert.That(elapsedFired);
Assert
will be examined before the timer fired - all thanks to our fast technologies.One remedy would be to make sure we wait long enough so the timer has a chance to fire by adding:
System.Threading.Thread.Sleep(200);
The revised test looks like this:
var mockTimer = new System.Timers.Timer(1);It will now pass. So problem solved? Not at all, just found a questionable workaround.
bool elapsedFired = false;
mockTimer.Elapsed += (sender, e) => elapsedFired = true;
mockTimer.Start();
System.Threading.Thread.Sleep(200);
Assert.That(elapsedFired);
This solution is bad for two reasons.
- The test could still fail whenever the timer thread is much slower than the one we are on.
- If we have a lot of tests like that, they will take a very long time, which needs to be avoided.
At first I tried to mock the timer using Moq, to at least verify, that the timer.Start() method got called. Unfortunately for unknown reasons Moq will claim, that it didn't get called at all even though I called it inside the test. So this doesn't seem to work.
Additionally, I would like to check, that the registered methods get invoked, when the Elapsed Event fires.
So I tried another route.
I created a class
FireOnStartTimer
that inherits from System.Timers.Timer
in which I tried to trigger the Elapsed event by hand, right when the timer is started, like so:public new void Start()Notice that I couldn't override the
{
this.Elapsed.Invoke(this, new EventArgs() as System.Timers.ElapsedEventArgs);
}
Start()
method, therefore I used new void Start()
instead.The compiler didn't like that solution though and gave me this error:
The event 'System.Timers.Timer.Elapsed' can only appear on the left hand side of += or -= (CS0079)
What to do?
I decided to "new" the Elapsed Eventhandler itself as well, hoping that since then my class "owns" it, I would be allowed to do what I tried previously.
I added:
public new event System.Timers.ElapsedEventHandler Elapsed;
Bingo! Now it compiled!
I ran the previous test, without the ugly
System.Threading.Thread.Sleep(200);:
var mockTimer = new FireOnStartTimer();
bool elapsedFired = false;
mockTimer.Elapsed += (sender, e) => elapsedFired = true;
mockTimer.Start();
Assert.That(elapsedFired);
And it passed still.Mission accomplished!
Here is the complete FireOnStartTimer class:
For convenience I have added a Constructor which sets up the timer for me, this is not required though, so this would be the essence of an hour of experimentation:
class FireOnStartTimer : System.Timers.Timer
{
public new event System.Timers.ElapsedEventHandler Elapsed;
public FireOnStartTimer() : base() {}
public FireOnStartTimer(double interval, ISynchronizeInvoke syncObject)
: this()
{
this.SynchronizingObject = syncObject;
this.AutoReset = false;
this.Enabled = true;
this.Interval = interval;
}
public new void Start()
{
this.Elapsed.Invoke(this, new EventArgs() as System.Timers.ElapsedEventArgs);
}
}
class FireOnStartTimer : System.Timers.Timer
{
public new event System.Timers.ElapsedEventHandler Elapsed;
public new void Start()
{
this.Elapsed.Invoke(this, new EventArgs() as System.Timers.ElapsedEventArgs);
}
}
Tiny but of great use.
Finally I needed to create a Timer interface and an adapter for the
System.Timers.Timer
that implements the interface.It is necessary to pass the timer into the system under test as
ITimer
in order to make use of polymorphism and in that way ensure that the Start()
method of our custom timer is called during testing. If we would just pass it in as System.Timers.Timer
, our Custom Start()
method is never invoked, but instead the one of System.Timers.Timer
. Also we want to avoid any
if (myTimer is FireOnStartTimer)
(myTimer as FireOnStartTimer).Start();
else
(myTimer as System.Timers.Timer
).Start();
kind of mess.
Since both Timer classes implement the
ITimer
interface, we can pass in our custom Timer during testing and the Adapter for the System.Timers.Timer
in the production code, without having to change the class we are testing.Here is that interface:
public interface ITimer
{
void Start();
void Stop();
void Close();
bool AutoReset { get; set; }
bool Enabled { get; set; }
double Interval { get; set; }
ISynchronizeInvoke SynchronizingObject { get; set; }
event ElapsedEventHandler Elapsed;
}
and the adapter:
///<summary>
/// Adapts the System.Timers.Timer class to implement the ITimer interface.
/// </summary>
public class SystemTimerAdapter : Timer, ITimer
{
public SystemTimerAdapter() : base() {}
public SystemTimerAdapter(double interval) : base(interval) {}
}
Of course other timers could also be used with above interface. All we need to do is write an adapter for them.
3 Comments:
Thanks - this is a really useful post that provides a good start on unit testing code involving timers. In my particular case, the timer was being used to poll a webserver at regular intervals, not just fire of a method never to be used again. This meant that I had to extend your FireOnStartTimer to be able to fire a certain number of times before unblocking an event which the unit test could wait on. Instead of inheriting from System.Timers.Timer, I ended up wrapping a System.Timers.Timer and used this internally (with an interval of 1ms) to kick off the invoke. This allowed multiple sequential invokes whilst still letting the call stack unwind. So it got a bit more complicated, but worked in the end.
Thanks :)
"new EventArgs() as System.Timers.ElapsedEventArgs" is null because a base object that is cast to a derived class refernce is null. This means that you might as well just pass in null.
However, this breaks the OO Liskov Substitution Principle. See http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod
I blame Microsoft for not having foresight that people actually want to unit test their timer based code and not providing the ability to create a mock timer that implements their own interface.
My suggestion is to do away with MS's ElapsedEventArgs class and create your own. This would mean less conformance to .Net but you aren't conforming to it anyway as you're using your own ITimer interface. Your SystemTimerAdapter class would become a wrapper (or be composed of a Sytem.Timers.Timer object) rather than inherit from it.
Great and that i have a neat give: How To Plan House Renovation hgtv home improvement
Post a Comment
Subscribe to Post Comments [Atom]
<< Home