Search code examples
c#.netunit-testingasync-await.net-6.0

Mock PeriodicTimer for UnitTests


My class is using PeriodicTimer . I want to mock its time in unit tests. Is it possible? I can set up the class to have a shorter period but that's not the best practice for unit testing.

Nsubstitute example is preferable, but does not really matter.

Maybe it's possible to make a wrapper, however, ValueTask is a bit more tricky. Maybe I need to dig into IValueTaskSource. But maybe someone has a solution?

The code I want to test:

public class Example : BackgroundService
     {
         private readonly PeriodicTimer _timer;
     
         public Example(IConfiguration configuration)
         {
             _timer = new PeriodicTimer(TimeSpan.Parse(configuration["Interval"]));
         }
         
         protected override async Task ExecuteAsync(CancellationToken stoppingToken)
         {
             while (await _timer.WaitForNextTickAsync(stoppingToken)) // I want to simulate this without waiting for real time
             {
                 // Do something
             }
         }
}

Solution

  • The wrapper is the way to go, and it should be a dependency for your class.

    Given these types

    public interface IPeriodicTimer : IDisposable
    {
        ValueTask<bool> WaitForNextTickAsync (CancellationToken cancellationToken = default);
    }
    
    public sealed class StandardPeriodicTimer : IPeriodicTimer
    {
        private PeriodicTimer _actualTimer;
    
        public StandardPeriodicTimer(TimeSpan timeSpan)
            => _actualTimer = new PeriodicTimer(timeSpan);
    
        public async ValueTask<bool> WaitForNextTickAsync(CancellationToken cancellationToken = default)
            => await _actualTimer.WaitForNextTickAsync(cancellationToken);
    
    
        public void Dispose() => _actualTimer.Dispose();
    }
    
    public class YourDependentClass
    {
        public YourDependentClass(IPeriodicTimer timer) => _timer = timer;
        ...
    }
    

    You can initialize a YourDependentClass via newing it up, or via dependency injection in production code.

    var dependent = new YourDependentClass(new StandardPeriodicTimer(TimeSpan.FromSeconds(1)));
    

    To support the tests, you can create a stub that simply returns true every time without any delay between.

    public sealed class TestPeriodicTimer : IPeriodicTimer
    {
        public async ValueTask<bool> WaitForNextTickAsync(CancellationToken cancellationToken = default)
            => await Task.FromResult(true);
    
        public void Dispose()
        {}
    }
    

    When you use this stub, we're assuming that somehow the timer stops based on some internal condition in the dependent class.

    [Test]
    public void TestTheDependentClass()
    {
        // arrange
        var timer = new TestPeriodicTimer();
        var dependent = new YourDependentClass(timer);
    
        // act
        dependent.DoSomething();
    
        // assert
        // method exits because timer loop is done, timer disposed, etc.
        ...
    }
    

    Now, it could get a little trickier if you want the timer to fire a certain number of times, and to run assertions for each tick of the timer. You can either switch from a stub to NSubstitute as you suggest, or carry your stub a step further in its implementation.

    But, it gets trickier still. The assertions should be about the state of the dependent after the dependent gets a return value for WaitForNextTickAsync. That means

    • upon the very first call to WaitForNextTickAsync you do nothing
    • on all subsequent calls but the final one you do assertions after the state changes
    • and finally, you have to do assertions when the timer is disposed

    I'll make this make sense. 😀

    The typical periodic timer is called in a loop. Let's say you call a method on your dependent that has this loop.

    while (await _timer.WaitForNextTickAsync())
    {
        // some work
        ...
    }
    

    Furthermore, let's say in your test code you want to assert on the state of the dependency after each time // some work happens, not before.

    And finally, the test run has to be finite. That means it must run a certain number of times, or the dependent must somehow exit the loop early. Let's code for the former problem.

    public sealed class TestPeriodicTimer : IPeriodicTimer
    {
        private readonly int _numberOfExpectedTicks;
        private int _tickNumber;
    
        // our list of assertions...
        // we assert for tick 1 on the second call to WaitForNextTickAsync
        // we assert for tick 2 on the third call to WaitForNextTickAsync
        // etc.
        // so let's emulate a list that "starts" at index 2 instead of 0 to
        // avoid some confusing code later
        private readonly List<Action<int, bool>> _assertions = new() { null, null };
    
        public TestPeriodicTimer(int numberOfTicks) => _numberOfExpectedTicks = numberofTicks.
    
        // this is how we know what to assert when...
        public void AddAssertionsCallback(Action<int, bool> action)
            => _actions.Add(action);
    
        public async ValueTask<bool> WaitForNextTickAsync(CancellationToken cancellationToken = default)
        {
            // get result for current tick
            var result = await Task.FromResult(_numberOfExpectedTicks == ++_tickNumber);
            
            if (_tickNumber != 1)
            {
                // assert for the prior tick
                var assertedTickNumber = _tickNumber - 1;
                _assertions[_tickNumber](_assertedTickNumber, result);
            }
              return result;
        }    
    
        public void Dispose()
        {
           if (_disposed) return;
           _disposed = true;
    
            // assert for the final tick
            _assertions[_tickNumber + 1](_tickNumber, result);
        }
    
        private bool _disposed;
    }
    
    

    Ok, now we can finally code an expressive test.

    [Test]
    public void TestThreeTicksOfTheTimerInYourDependentClass()
    {
        // arrange
        var timer = new TestPeriodicTimer(3);
        var dependent = new YourDependentClass(timer);
    
        dependent.AddAssertionsCallback((tickNumber, isFinalTick) =>
        {
            // assertions you expect to be true for tick 1 (actually
            // the second call to WatiForNextTickAsync)
            ...
            // and just to make sure...
            Assert.IsTrue(tickNumber, 1);
            Assert.IsFalse(isFinalTick);
        });
    
        dependent.AddAssertionsCallback((tickNumber, isFinalTick) =>
        {
            // assertions you expect to be true for tick 2 (actually
            // the third call to WatiForNextTickAsync)
            ...
            // and just to make sure...
            Assert.IsTrue(tickNumber, 2);
            Assert.IsFalse(isFinalTick);
        });
    
        dependent.AddAssertionsCallback((tickNumber, isFinalTick) =>
        {
            // assertions you expect to be true for tick 3 (actually
            // the call to Dispose(), because Dispose() happens after
            // tick 3's work
            ...
            // and just to make sure...
            Assert.IsTrue(tickNumber, 3);
            Assert.IsTrue(isFinalTick);
        });
    
        // act
        dependent.DoSomething();
        timer.Dispose();
    
        // assert
        // place any final assertions here as usual
    }
    

    This test says I want exactly 3 ticks to occur with the final (third) tick to of course be the last.

    Wow. that was a lot of work. But this gives you a way to

    • not sleep/block during a test
    • test assertions for each and every tick if desired

    A further improvement would be to share assertions across ticks, etc.