NSubstitute - make async method (Task) throw an exception

NSubstitute - make async method (Task) throw an exception

The other day I was writing a unit test to verify what was supposed to happen when a Task would throw an exception when awaited. It wasn't as straight forward as I first assumed, but I quickly found this Stack Overflow post that helped me:

Normally, for synchronous calls, you can just add a .Throws<TException>() after the method you want to mock throwing an exception (or .ThrowsForAnyArgs<TException>() if you don't care about the input arguments). If you do this on a method returning a Task then that call would instantly throw, which is - most often - not what happens; it's the work being done by the Task throwing an exception, not the action of creating the Task.

As you can read in the Stack Overflow post the way to simulate a Task throwing an exception is to have the method return an already faulted Task, which can be done (sort of easily) with the static method Task.FromException<TReturn>(Exception).

interface IService
{
    Task<MyModel> GetAsync();
}
var service = Substitute.For<IService>();
service.GetAsync().Returns(Task.FromException<MyModel>(new ArgumentException("bla bla"));

But that's a bit too verbose for my taste, compared to this synchronous version:

var service = Substitute.For<IService>();
service.Get().Throws<ArgumentException>();

So I ended up creating a few extension methods enabling a more simple and readable syntax, so I could instead write it like this:

var service = Substitute.For<IService>();

// Any Exception
service.GetAsync().TaskThrowsException("optional message");

// Specific Exception v1
service.GetAsync().TaskThrows<MyModel, ArgumentException>("optional message");
// Specific Exception v2
service.GetAsync().TaskThrows(typeof(ArgumentException), "optional message");

// Specific Exception instance
service.GetAsync().TaskThrows(new ArgumentException());

That's a lot cleaner and more readable in my eyes :)

The TaskThrows<MyModel, ArgumentException>() might seem a little weird, but for the generic Task<T> we sadly can't have the generic arguments inferred as we can for the others :(

Here are all the extension methods for both Task and Task<T>:

public static class NSubstituteExtensions
{
    public static ConfiguredCall TaskThrowsException<T>(this Task<T> task, string message = null)
    {
        return TaskThrows<T, Exception>(task, message);
    }

    public static ConfiguredCall TaskThrows<T, TException>(this Task<T> task, string message = null)
        where TException : Exception
    {
        return TaskThrows(task, typeof(TException));
    }

    public static ConfiguredCall TaskThrows<T>(this Task<T> task, Type exceptionType, string message = null)
    {
        if (!typeof(Exception).IsAssignableFrom(exceptionType))
            throw new ArgumentException($"Type has to be a subclass of System.Exception", nameof(exceptionType));

        var exception = Activator.CreateInstance(exceptionType, message) as Exception;
        return TaskThrows(task, exception);
    }

    public static ConfiguredCall TaskThrows<T>(this Task<T> task, Exception exception)
    {
        return task.Returns(Task.FromException<T>(exception));
    }

    public static ConfiguredCall TaskThrows<TException>(this Task task, string message = null)
        where TException : Exception
    {
        return TaskThrows(task, typeof(TException));
    }

    public static ConfiguredCall TaskThrows(this Task task, Type exceptionType, string message = null)
    {
        if (!typeof(Exception).IsAssignableFrom(exceptionType))
            throw new ArgumentException($"Type has to be a subclass of System.Exception", nameof(exceptionType));

        var exception = Activator.CreateInstance(exceptionType, message) as Exception;
        return TaskThrows(task, exception);
    }

    public static ConfiguredCall TaskThrows(this Task task, Exception exception)
    {
        return task.Returns(Task.FromException(exception));
    }
}