Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Moq Throw async exception in one of tasks in call to Task.WhenAll

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)));
like image 638
Farhad-Taran Avatar asked Apr 05 '16 15:04

Farhad-Taran


2 Answers

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."))
);
like image 85
Stephen Cleary Avatar answered Sep 18 '22 01:09

Stephen Cleary


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"); }));
like image 28
Bruno Garcia Avatar answered Sep 20 '22 01:09

Bruno Garcia