desautelsj desautelsj - 25 days ago 9
C# Question

Mocking Task.Delay

I have a method with the following line:

await Task.Delay(waitTime).ConfigureAwait(false);


I there a good strategy to avoid actually waiting the few seconds when unit testing and instead verify that we tried to wait a specific number of seconds.

For instance, is there a way to inject an additional parameter into my method like in this (contrived) example where I inject a mocked object of a fictitious
ITaskWaiter
interface:

// Arrange
var mockWait = new Mock<ITaskWaiter>(MockBehavior.Strict);
mockWait.Setup(w => w.Delay(It.Is<TimeSpan>(t => t.TotalSeconds == 2)));

// Act
myObject.MyMethod(mockWait.Object);

// Assert
mockWait.Verify();

Answer

You can define a "delayer" interface like this:

public interface IAsyncDelayer
{
    Task Delay(TimeSpan timeSpan);
}

And then you can provide the following implementation for production code:

public class AsyncDelayer : IAsyncDelayer
{
    public Task Delay(TimeSpan timeSpan)
    {
        return Task.Delay(timeSpan);
    }
}

Now, your class would look something like this:

public class ClassUnderTest
{
    private readonly IAsyncDelayer asyncDelayer;

    public ClassUnderTest(IAsyncDelayer asyncDelayer)
    {
        this.asyncDelayer = asyncDelayer;
    }

    public async Task<int> MethodUnderTest()
    {
        await asyncDelayer.Delay(TimeSpan.FromSeconds(2));

        return 5;
    }
}

This is basic application of Dependency Injection. Basically, we extracted the logic of asynchronously waiting to a different class and created an interface for it to enable polymorphism.

In production, you would compose your object like this:

var myClass = new ClassUnderTest(new AsyncDelayer());

Now, in your test you can create a fake delayer that returns immediately like this:

[TestMethod]
public async Task TestMethod1()
{
    var mockWait = new Mock<IAsyncDelayer>();

    mockWait.Setup(m => m.Delay(It.IsAny<TimeSpan>())).Returns(Task.FromResult(0));

    var sut = new ClassUnderTest(mockWait.Object);

    var result = await sut.MethodUnderTest();

    Assert.AreEqual(5, result);
}
Comments