Saab.Time.Fakes 1.0.0

The ID prefix of this package has been reserved for one of the owners of this package by NuGet.org. Prefix Reserved
dotnet add package Saab.Time.Fakes --version 1.0.0
NuGet\Install-Package Saab.Time.Fakes -Version 1.0.0
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Saab.Time.Fakes" Version="1.0.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Saab.Time.Fakes --version 1.0.0
#r "nuget: Saab.Time.Fakes, 1.0.0"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Saab.Time.Fakes as a Cake Addin
#addin nuget:?package=Saab.Time.Fakes&version=1.0.0

// Install Saab.Time.Fakes as a Cake Tool
#tool nuget:?package=Saab.Time.Fakes&version=1.0.0

fake-time

Fake Time is a set of utilities for controlling the passage of time during a test run.

Useful scenarios for this library are:

  • I am using a CancellationToken which should be automatically cancelled in X seconds
  • I am using a timer, and I want to see how my class behaves as time progresses
  • I need to call Task.Delay as part of my workflow

In all of the scenarios above, we would like our test run to be fast and predictable.

Using the library

We use dependancy injection to allow us control the passage of time during a test run. Delegates are provided to allow the fake to be injected during a test run, and the real implementation to be injected for production use.

Using FakeTime in your tests

Declare an instance of FakeTime for your test run:

    FakeTime fakeTime = new FakeTime();

Set the current time (optional)

    fakeTime.CurrentTime = new DateTime(2022, 1, 1, 0, 0, 0);

Move the time on in your test when you wish

    // arrange
    var delayTask = faketime.DelayAsync(TimeSpan.FromSeconds(5));

    // act
    fakeTime.AdvanceTime(TimeSpan.FromSeconds(5));

    // assert
    await delayTask;

A more complex example using a real class

    [Test]
    public async Task Execute_Always_RunsFastEvenAsync()
    {
        // arrange
        var example =
            new Example(
                NullLogger<Example>.Instance,
                () => fakeTime.CurrentTime,
                fakeTime.CreateTimer,
                fakeTime.DelayAsync,
                fakeTime.CreateCancellationTokenSource);

        var executeTask = example.ExecuteAsync(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(5));

        // act
        fakeTime.AdvanceTime(TimeSpan.FromSeconds(15));

        // assert
        await executeTask;
    }

Using FakeTime in your classes

Inject the required functions into your class, if you wanted to inject everything it would look something like this.

    public Example(ILogger<Example> logger, UtcNow utcNow, CreateTimer createTimer, TaskDelay taskDelay, CreateCancellationTokenSource createCancellationTokenSource)
    {
        this.logger = logger;
        this.utcNow = utcNow;
        this.createTimer = createTimer;
        this.taskDelay = taskDelay;
        this.createCancellationTokenSource = createCancellationTokenSource;
    }
Using timer
    public void StartTimer(TimeSpan timerTickPeriod)
    {
        this.timer = createTimer(); // remember to dispose this as appropriate
        timer.Interval = timerTickPeriod.TotalMilliseconds;
        timer.Elapsed += Timer_Elapsed;
        timer.Start();
    }

    private void Timer_Elapsed(object? sender, ElapsedEventArgs e)
    {
        logger.LogTrace($"Timer elapsed at {utcNow()}");
    }
Using CancellationTokenSource
    public async Task UseCancellationToken(TimeSpan cancellationTimeout)
    {
        using var cts = createCancellationTokenSource(cancellationTimeout);
        var tcs = new TaskCompletionSource<object>();
        using (cts.Token.Register(() => tcs.TrySetResult(new object())))
        {
            await tcs.Task;

            logger.LogTrace($"Task was cancelled at {utcNow()}");
        }
    }
Using TaskDelay
    public async Task UseTaskDelay(TimeSpan delayTimeout, CancellationToken ct = default)
    {
        // Using TaskDelay
        await taskDelay(delayTimeout, ct);

        logger.LogTrace($"Delay completed at {utcNow()}");
    }

Registering for a test run

    private FakeTime FakeTime { get; } = new FakeTime();

    private static void AddTime(this IServiceCollection services)
    {
        services.AddSingleton<UtcNow>(() => FakeTime.CurrentTime);
        services.AddSingleton<CreateTimer>(FakeTime.CreateTimer);
        services.AddSingleton<TaskDelay>(FakeTime.DelayAsync);
        services.AddSingleton<CreateCancellationTokenSource>(FakeTime.CreateCancellationTokenSource);
    }

Registering for production

    private static void AddTime(this IServiceCollection services)
    {
        services.AddSingleton<UtcNow>(() => DateTime.UtcNow);
        services.AddSingleton<CreateTimer>(() => new SystemTimer());
        services.AddSingleton<TaskDelay>((t, ct) => Task.Delay(t, ct));
        services.AddSingleton<CreateCancellationTokenSource>(t => new CancellationTokenSourceWrapper(t));
    }

Timers

Due to the large number of implementations of Timer in .NET, we have chosen to use an abstraction and allow the user to implement the real ITimer. The ITimer interface maps exactly to the System.Timers.Timer class, and so if you're only using that implementation of Timer it can be implemented in 1 line of code:

public class SystemTimer : System.Timers.Timer, ITimer { }

CancellationTokenSource

At this time a wrapper for CancellationTokenSource is not provided. This can be implemented in 1 line of code.

public class CancellationTokenSourceWrapper : CancellationTokenSource, ICancellationTokenSource { }

Limitations

  • The Fake CancellationTokenSource CancelAfter(Int32) method does not support 0 or -1 values like the real CancellationTokenSource

Gotchas

  • Setting FakeTime.CurrentTime does not have any effect on the other Fakes being used, this is by design. Use the FakeTime.AdvanceTime(TimeSpan) method to advance time and see the effect on the fakes.
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.0.0 1,432 4/26/2022
0.2.1 389 4/26/2022
0.2.0 510 4/7/2022
0.1.0 411 4/7/2022