Reputation: 1103
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
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
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