I have a number of Tasks that I am passing to a Task.WhenAll call. In my tests, I am setting up the first task to throw an exception, but my call to Tasks.WhenAll is not completed for all the tasks and immediatly breaks when the exception is thrown. My assumption was that all tasks will be completed and returned when a call to Tasks.WhenAll is made, so I think that Moq is not able to make distinctions between throwing async and non-async exceptions.
I want to be able to allow all the tasks to run, and once all of them are completed, I want to grab the faulted tasks and extract their exceptions and act accordingly. Is my implementation correct? Is Moq unable to cope or do I have to fix my implementation and the problem is not with Moq?
public async Task StartAsync(TextWriter log)
{
log = TextWriter.Synchronized(log);
var processorTaks = _processors.Select(x => x.StartAsync(log)).ToList();
Task.WhenAll(processorTaks).Wait();
var innerExceptions = processorTaks.Where(x => x.Exception != null)
.Select(x => x.Exception.InnerException)
.ToList();
if (innerExceptions.Any())
{
innerExceptions.AddRange(_processors.SelectMany(x => x.NotifierException).ToList());
var formattedExceptions = innerExceptions
.Select(ex => $"{ex.Message}<\br>{ex.StackTrace}")
.Distinct();
IEnumerable<Task> sendEmailTaks = _alertEmailAddresses
.Split('|')
.Select(
x =>
_smtpClient.SendMailAsync($"Notifications failed with {innerExceptions.Count()} exceptions",
string.Join("<\br>", formattedExceptions), x));
var retry = _createWebRetry();
await retry.RetryAsync(() => Task.WhenAll(sendEmailTaks), CancellationToken.None);
}
}
My setup for the processors:
FirstProcessor.Setup(x => x.StartAsync(It.IsAny<TextWriter>())).Throws(new Exception("some exception happened."));
SecondProcessor.Setup(x => x.StartAsync(It.IsAny<TextWriter>())).Returns(Task.FromResult(default(object)));
As Bruno correctly pointed out, the problem is that the mocked StartAsync
is throwing an exception synchronously, not returning a faulted task.
However, the correct code cannot use new Task
(which will cause a hang, since the task is never started). Instead, use Task.FromException
:
FirstProcessor.Setup(x => x.StartAsync(It.IsAny<TextWriter>())).Returns(
Task.FromException(new Exception("some exception happened."))
);
Task.WhenAll
will call StartAsync
, that will throw.
The exception is thrown on the calling thread. Before a task could be created.
You want StartAsync
to return a Task
:
firstProcessor.Setup(x => x.StartAsync(It.IsAny<TextWriter>())).Returns(new Task(() => { throw new Exception("err"); }));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With