1

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).

bakunet
  • 559
  • 8
  • 25

1 Answers1

1

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
}
Michael
  • 3,350
  • 2
  • 21
  • 35
  • At the beginning I was worrying that, that your solution does not use `TimerCallback` in constructor, but I realized, that it fixes many problems related with triggering callback by constructor in my solution. I will try to implement your way and I will come back soon... Thank you. – bakunet Jun 11 '20 at 19:57
  • Ok, now I think that I get everyting right and understand what I did wrong. I used similar to your approach, with **Moq**, but anyway, I can accept your answer. – bakunet Jun 11 '20 at 21:01