Julian
Julian

Reputation: 1103

async task testing

I just started using Rhino Mocks that test my async methods within the code. But while normally the code works just perfect but while mocking tests with Rhino Mocks there are some weird async problems.

Let's say I want to test the following code which calls multiple numberGenerators that all will run at the same time and wait till they are all done:

public async Task MyTask()
{
    List<Task<List<int>>> numberTasks = new List<Task<List<int>>>();

    foreach (var numberGenerator in numberGenerators)
    {
        // GetNumbers is an 'async Task<List<int>>'
        // As you can see I don't use 'await' so it should just add it and go on.
        numberTasks.Add(numberGenerator.GetNumbers()); 
    }

    try
    {
        await Task.WhenAll(numberTasks);
    }
    catch (Exception e)
    {
        // Log exception if something happens at 'GetNumbers' (like time-out or whatever)
    }

    var numbers = new List<int>();
    numbers.AddRange(numberTasks.Where(task => task.Status == TaskStatus.RanToCompletion).SelectMany(task => task.Result));

    // Do something with the numbers
}

Is tested with:

numberGenerator.Expect(x => x.GetNumbers())
    .WhenCalled(x => Thread.Sleep(10000))
    .Return(Task.FromResult(numbers));

I use two of the generators on 'mocked' in the code above. Now if I run the code with 'non-mocked' objects numberTasks.Add just adds the Task and moves on. But when I mock it with the WhenCalled sleep it waites for 10 seconds before going to the next one in the foreach loop.

How can I change my mock that it acts like a normal async Task that takes 10 seconds to complete?

Bonus question: Can want to be able to do the same with .Throw() so I can wait 10 seconds before throwing an Exception.

Update: Although I thought the following would fix the problem:

numberGenerator.Expect(x => x.GetNumbers())
    .Return(Task.Delay(10000).ContinueWith(x => numbers));

This doesn't seem to be the case. The Task.Delay starts counting down at the moment this mock is created. So if I create this mock, wait 10 seconds and then run the test it will be done in 0 seconds.

So my question remains, how can I test an async method with a delay before returning the result.

Upvotes: 1

Views: 1393

Answers (2)

i3arnon
i3arnon

Reputation: 116548

An async method runs synchronously until an await is reached. That means if you wait before returning a task in the mocked operation this wait will be synchronous. What you want to do is return a task immediately that completes after some time.

You can do that with Task.Delay:

numberGenerator.Expect(x => x.GetNumbers()).
        Return(Task.Delay(10000).ContinueWith(t => numbers));

Task.ContinueWith is used to add the actual return value after the delay is completed.

If you would like to throw an exception instead of returning a value, that too can be done inside the continuation.

Since Task.Delay(10000).ContinueWith(t => numbers) is evaluated when you create the mock, each call would return the same task (which will be completed 10 seconds after the mock was created).

If you need to create a new task each time you need to pass a delegate. Return doesn't accept a delegate so you need to use WhenCalled:

numberGenerator.Expect(x => x.GetNumbers()).
        WhenCalled(mi => mi.ReturnValue = Task.Delay(10000).ContinueWith(x => numbers));

Now, while WhenCalled is used to set the return value using Return is still required in order to determine the type of the return result. It doesn't matter what value it gets as the return value will still be set by WhenCalled:

numberGenerator.Expect(x => x.GetNumbers()).
    WhenCalled(mi => mi.ReturnValue = Task.Delay(10000).ContinueWith(x => numbers)).
    Return(Task.FromResult(new List<int>()));

Upvotes: 2

Tseng
Tseng

Reputation: 64150

Personally I haven't worked with rhino mock, so I don't know if it supports async Method calls.

But Thread.Sleep(10000) is a synchronous code. If you want await in asynchronously you have to use Task.Delay(10000).

Or alternatively use Task.Run( () => Thread.Sleep(10000) ) though, Task.Delay(10000) is preferred way and best practice.

Upvotes: 1

Related Questions