desautelsj
desautelsj

Reputation: 3715

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();

Upvotes: 14

Views: 7523

Answers (2)

lonix
lonix

Reputation: 20579

The accepted answer is the correct approach.

Alternative 1

If you want to "hide" the dependency from client code (in different assemblies), but allow tests to use it:

public class ClassUnderTest
{
    private readonly IAsyncDelayer asyncDelayer;

    public ClassUnderTest() : this(new AsyncDelayer()) { }   // used by client code

    internal ClassUnderTest(IAsyncDelayer asyncDelayer)      // used by tests
    {
        this.asyncDelayer = asyncDelayer;
    }

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

Alternative 2

If you don't want to introduce the mockable dependency (for whatever reason), then extract the delay to a virtual method and mock that:

public class ClassUnderTest
{
    internal virtual Task Delay(TimeSpan delay) => Task.Delay(delay);

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

In the test, you need to partial mock the class. Then mock Delay() to return Task.CompletedTask so it does nothing. You can also verify that the mocked method was called with the value 2.

Upvotes: 0

Yacoub Massad
Yacoub Massad

Reputation: 27861

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);
}

Upvotes: 26

Related Questions