Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I unit test code that contain Task.Delay?

How can I unit test component that has an await Task.Delay without really having to wait for it.

For example,

public void Retry()
{
    // doSomething();
    if(fail)
       await Task.Delay(5000);
}

I need to test the failure branch but I do not want my test to wait for 5 seconds.

Is there anything like rx virtual time-based scheduling available in task parallel library?

like image 556
Misterhex Avatar asked Jul 29 '13 16:07

Misterhex


3 Answers

A timer is just another form of external dependency. The only difference is that it depends on the clock instead of a database. And like any other external dependency, it makes testing hard; the answer, therefore, is to improve the design until it's easy to test.

Following the Dependency Injection principle suggests that you should pass the timer in on the object's constructor, which enables you to inject a test stub instead of relying on a real timer.

Note how this can improve your design. In many cases, the timeout period varies based on who's calling. This moves the responsibility for establishing the timeout period to the layer of your application that's waiting on it. That is the layer that should understand how long it's willing to wait.

like image 54
John Deters Avatar answered Oct 18 '22 08:10

John Deters


Is there anything like rx virtual time-based scheduling available in task parallel library?

No, there is not. Your options are to either define a "timer service" that you can implement with a test stub, or using Microsoft Fakes to intercept the call to Task.Delay. I prefer the latter, but it is only an option on VS Ultimate.

like image 30
Stephen Cleary Avatar answered Oct 18 '22 07:10

Stephen Cleary


1) Define your own implementation of Task.Delay.

public static class TaskEx
{
    private static bool _shouldSkipDelays;

    public static Task Delay(TimeSpan delay)
    {
        return _shouldSkipDelays ? Task.FromResult(0) : Task.Delay(delay);
    }

    public static IDisposable SkipDelays()
    {
        return new SkipDelaysHandle();
    }

    private class SkipDelaysHandle : IDisposable
    {
        private readonly bool _previousState;

        public SkipDelaysHandle()
        {
            _previousState = _shouldSkipDelays;
            _shouldSkipDelays = true;
        }

        public void Dispose()
        {
            _shouldSkipDelays = _previousState;
        }
    }
}

2) Use TaskEx.Delay instead of Task.Delay everywhere in your code.

3) In your tests use TaskEx.SkipDelays:

[Test]
public async Task MyTest()
{
    using (TaskEx.SkipDelays())
    {
        // your code that will ignore delays
    }
}
like image 5
alexey Avatar answered Oct 18 '22 08:10

alexey