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
}
}
}
CodePudding user response:
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 new
ing 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.