Search code examples
c#unit-testingtimermoqwrapper

How to make a testable wrapper around Threading.Timer and replace it when unit testing?


I am struggling more than one day already with testing of my System.Threading.Timer(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period); overload.

Basing on this solution I created my ThreadingTimer, FakeTimer, that implements ITimer:

public interface ITimer
    {
        bool Change(int dueTime, int period);
        bool IsDisposed { get; }

        void Dispose();
    }


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

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

        public bool IsDisposed { get; set; }

        public void Dispose()
        {
            IsDisposed = true;
            Dispose();
            GC.SuppressFinalize(this);
        }


        public bool Change(int dueTime, int period)
        {
            _timer.Change(dueTime, period);

            return true;
        }
    }

public class FakeTimer : ITimer
    {
        private object state;

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

        public bool IsDisposed { get; set; }

        public bool Change(int dueTime, int period)
        {
            return true;
        }

        public void Dispose()
        {
            IsDisposed = true;
            Dispose();
            GC.SuppressFinalize(this);
        }
    }

In my service class I want to make usage of my ThreadingTimer:

public ITimer Timer { get; set; }
private void StartUpdates()
        {
            Task.Run(() =>
            {
                Timer = new ThreadingTimer(StartUpdatingVessels, null, TimeSpan.Zero, TimeSpan.FromHours(24));

            }, Token);
        }

BUT when it comes to unit testing, I have no idea and understanding how to make usage and advantage of implementing of ITimer in my FakeTimer. Because if in my test will be called method StartUpdates(), new instance of ThreadingTimer will be created, even if already Timer prop has assigned FakeTimer:

[Fact]
        public void SetUpdatingStarted_CalledWhenUpdatingAlreadyStarted_DisposesTimer()
        {
            _timedUpdateControl.Timer = new FakeTimer(CallbackTestMethod, null);
            bool intendedToStartUpdates = false;

            _timedUpdateControl.StartOrStopUpdates(intendedToStartUpdates); //that methid calls private method StartUpdates() and creates instance for ITimer

            //assert something
        }

How hould I mock it there? (I am using Moq framework in test project).


Solution

  • One solution is to use Dependency Injection:

    public interface ITimer
    {
        event Action Elapsed;
        void StartTimer(int dueTime, int period);
        bool IsDisposed { get; }
    
        void Dispose();
    }
    
    public class TimedUpdateControl
    {
        private ITimer timer;
    
        public TimedUpdateControl(ITimer timer)
        {
            this.timer = timer;
            this.timer.Elapsed += () => { // do something }
        }
    
        private void StartUpdates()
        {
            Task.Run(() =>
            {
                timer.StartTimer(TimeSpan.Zero, TimeSpan.FromHours(24));
            }, Token);
        }
    }
    

    The test:

    [Fact]
    public void SetUpdatingStarted_CalledWhenUpdatingAlreadyStarted_DisposesTimer()
    {
        var timedUpdateControl = new TimedUpdateControl(new FakeTimer());
        bool intendedToStartUpdates = false;
    
        timedUpdateControl.StartOrStopUpdates(intendedToStartUpdates); 
    
        //assert something
    }