I have been searching for a solution for 3 days. I've got this one article but unfortunately, it uses a library that's no longer available (https://www.codeproject.com/Articles/19597/How-to-Test-a-Class-Which-Uses-DispatcherTimer). So I'm asking in hope for a new solution, or more elegant way to unit test a class that uses a DispatcherTimer.
Here's the code of my SUT:
public class RemainingTimeChangedEventArgs : EventArgs
{
public TimeSpan RemainingTime { get; init; }
public TimeSpan TimerSetFor { get; init; }
}
public class ExtendedTimer
{
public event EventHandler<RemainingTimeChangedEventArgs>? RemainingTimeChanged;
private readonly DispatcherTimer _timer;
private TimeSpan _timerSetFor = TimeSpan.Zero;
private TimeSpan _remainingTime = TimeSpan.Zero;
public TimeSpan Interval
{
get => _timer.Interval;
set => _timer.Interval = value;
}
public ExtendedTimer()
{
_timer = new();
_timer.Tick += OnTimeChanged;
_timer.Interval = TimeSpan.FromSeconds(1);
}
public void Initialize(TimeSpan timerSetFor)
{
_timerSetFor = timerSetFor;
_remainingTime = timerSetFor;
}
public void Start()
{
_timer.Start();
}
public void Resume()
{
_timer.Start();
}
public void Pause()
{
_timer.Stop();
}
public void Stop()
{
_timer.Stop();
}
private void OnTimeChanged(object? sender, EventArgs e)
{
_remainingTime -= Interval;
if (_remainingTime == TimeSpan.Zero) _timer.Stop();
RemainingTimeChanged?.Invoke(this, new RemainingTimeChangedEventArgs
{
RemainingTime = _remainingTime,
TimerSetFor = _timerSetFor
});
}
~ExtendedTimer() {
_timer.Tick -= OnTimeChanged;
}
So when the user starts the timer and TimerSetFor is initialized for a given time, it'll raise an event to tell the remaining time every tick/interval elapsed. Once the remaining time reaches 0, it'll raise the event and stop the ticking.
Currently, this is my xUnit test. I use Task.Delay, it's failed, because delaying the task means delaying the Dispatcher Timer to execute as well? But I hope it reveals my intention:
public class ExtendedTimerTests
{
private readonly ExtendedTimer _sut = new();
[Theory]
[InlineData(10, 100, 10)]
[InlineData(5, 50, 5)]
[InlineData(20, 30, 1)]
public async Task Start_GiveCorrectRemainingTimeEveryIntervalElapsed(
int interval, int timerSetFor, int expectedExecutedFor)
{
_sut.Interval = TimeSpan.FromMilliseconds(interval);
var ticksCount = 0;
void OnSutOnRemainingTimeChanged(object? sender, RemainingTimeChangedEventArgs args)
{
ticksCount++;
}
_sut.RemainingTimeChanged += OnSutOnRemainingTimeChanged;
_sut.Initialize(TimeSpan.FromMilliseconds(timerSetFor));
_sut.Start();
await Task.Delay(TimeSpan.FromMilliseconds(timerSetFor));
Assert.Equal(expectedExecutedFor, ticksCount);
// make sure the ticksCount still the same even after we wait for another time
await Task.Delay(TimeSpan.FromMilliseconds(50));
Assert.Equal(expectedExecutedFor, ticksCount);
}
}