Unit test System.Threading.Timer in .NET
Asked Answered
E

4

12

How to unit test a timer based on System.Threading.Timer in .NET The System.Threading.Timer has a callback method

Ettore answered 2/3, 2010 at 22:38 Comment(0)
M
11

You can unit-test it by not actually creating a direct dependency on System.Threading.Timer. Instead, create an ITimer interface, and a wrapper around System.Threading.Timer that implements it.

First you need to convert the callback to an event, so that it can be made part of an interface:

public delegate void TimerEventHandler(object sender, TimerEventArgs e);

public class TimerEventArgs : EventArgs
{
    public TimerEventArgs(object state)
    {
        this.State = state;
    }

    public object State { get; private set; }
}

Then create an interface:

public interface ITimer
{
    void Change(TimeSpan dueTime, TimeSpan period);
    event TimerEventHandler Tick;
}

And a wrapper:

public class ThreadingTimer : ITimer, IDisposable
{
    private Timer timer;

    public ThreadingTimer(object state, TimeSpan dueTime, TimeSpan period)
    {
        timer = new Timer(TimerCallback, state, dueTime, period);
    }

    public void Change(TimeSpan dueTime, TimeSpan period)
    {
        timer.Change(dueTime, period);
    }

    public void Dispose()
    {
        timer.Dispose();
    }

    private void TimerCallback(object state)
    {
        EventHandler tick = Tick;
        if (tick != null)
            tick(this, new TimerEventArgs(state));
    }

    public event TimerEventHandler Tick;
}

Obviously you would add whatever overloads of the constructor and/or Change method you need to use from the Threading.Timer. Now you can unit test anything depending on ITimer with a fake timer:

public class FakeTimer : ITimer
{
    private object state;

    public FakeTimer(object state)
    {
        this.state = state;
    }

    public void Change(TimeSpan dueTime, TimeSpan period)
    {
        // Do nothing
    }

    public void RaiseTickEvent()
    {
        EventHandler tick = Tick;
        if (tick != null)
            tick(this, new TimerEventArgs(state));
    }

    public event TimerEventHandler Tick;
}

Whenever you want to simulate a tick, just call RaiseTickEvent on the fake.

[TestMethod]
public void Component_should_respond_to_tick
{
    ITimer timer = new FakeTimer(someState);
    MyClass c = new MyClass(timer);
    timer.RaiseTickEvent();
    Assert.AreEqual(true, c.TickOccurred);
}
Manipur answered 2/3, 2010 at 23:26 Comment(4)
This looks good, but why do you create your own delegate rather than just using the TimerCallback delegate from the BCL?Selby
@Shaun: Because that delegate is not a valid event handler. Event handlers have a specific signature: object sender, TEventArgs e where TEventArgs : EventArgs.Manipur
Mind you, these days with closures being so readily supported, if I wanted something quick and dirty, I don't know if I'd bother with all of the event declaration and wire-up, I might just pass in an Action instead.Manipur
Good point regarding the standard event handler signature, that's bitten me before when trying to use Moq to raise events that have non-standard signatures.Selby
T
1

I will test it in the same way as any other class but with short time intervals as to avoid the unit test to run a long time. Another approach is to test your own code only and using a mock timer (eg. NMock), but it depends how your code design is. Can you post some code snippets?

Taryn answered 2/3, 2010 at 22:44 Comment(0)
S
0

If the time simply changes the callback method then you just need to check the correctness of that method, not how the system time works.

Besides that, I recommend using the MultimediaTimer instead - it is much more accurate.

Sixteenmo answered 2/3, 2010 at 22:46 Comment(2)
Say I want a timer that fires once an hour. Do you recommend the MultimediaTimer for everyone? :)Zinnes
Yes, and combine it with .NET's Dispatcher if you need to return the UI's thread.Sixteenmo
T
0

Hopefully whatever this feature is a larger part of allows the timer interval to be configured. You should be able to use that interface to inject a short interval suitable for unit testing. I do have the question the value of this test: Are you testing the interval (pointless) or that the routine is actually called approximately every so often?

Tobin answered 2/3, 2010 at 23:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.