Skip to content

Commit

Permalink
Add .ThrowsAsync() that will correctly mock exception on async meth…
Browse files Browse the repository at this point in the history
…ods. (nsubstitute#609)
  • Loading branch information
Socolin committed Aug 29, 2021
1 parent 680df07 commit f84efda
Show file tree
Hide file tree
Showing 4 changed files with 527 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
### unreleased
* [NEW] Add .NET 5 support
* [NEW] Add `.ThrowsAsync()` that will correctly mock exception on async methods. (#609)

### 4.2.2 (Jun 2020)

Expand Down
181 changes: 181 additions & 0 deletions src/NSubstitute/Extensions/ExceptionExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
using System;
using System.Linq;
using System.Reflection;
#if !NET45
using System.Threading.Tasks;
#endif
using NSubstitute.Core;

// Disable nullability for client API, so it does not affect clients.
Expand Down Expand Up @@ -67,5 +72,181 @@ public static ConfiguredCall ThrowsForAnyArgs<TException>(this object value)
/// <returns></returns>
public static ConfiguredCall ThrowsForAnyArgs(this object value, Func<CallInfo, Exception> createException) =>
value.ReturnsForAnyArgs(ci => throw createException(ci));

#if !NET45
/// <summary>
/// Throw an exception for this call.
/// </summary>
/// <param name="value"></param>
/// <param name="ex">Exception to throw</param>
/// <returns></returns>
public static ConfiguredCall ThrowsAsync(this Task value, Exception ex) =>
value.Returns(_ => Task.FromException(ex));

/// <summary>
/// Throw an exception for this call.
/// </summary>
/// <param name="value"></param>
/// <param name="ex">Exception to throw</param>
/// <returns></returns>
public static ConfiguredCall ThrowsAsync<T>(this Task<T> value, Exception ex) =>
value.Returns(_ => Task.FromException<T>(ex));

/// <summary>
/// Throw an exception of the given type for this call.
/// </summary>
/// <typeparam name="TException">Type of exception to throw</typeparam>
/// <param name="value"></param>
/// <returns></returns>
public static ConfiguredCall ThrowsAsync<TException>(this Task value)
where TException : notnull, Exception, new()
{
return value.Returns(_ => FromException(value, new TException()));
}

/// <summary>
/// Throw an exception for this call, as generated by the specified function.
/// </summary>
/// <param name="value"></param>
/// <param name="createException">Func creating exception object</param>
/// <returns></returns>
public static ConfiguredCall ThrowsAsync(this Task value, Func<CallInfo, Exception> createException) =>
value.Returns(ci => Task.FromException(createException(ci)));

/// <summary>
/// Throw an exception for this call, as generated by the specified function.
/// </summary>
/// <param name="value"></param>
/// <param name="createException">Func creating exception object</param>
/// <returns></returns>
public static ConfiguredCall ThrowsAsync<T>(this Task<T> value, Func<CallInfo, Exception> createException) =>
value.Returns(ci => Task.FromException<T>(createException(ci)));

/// <summary>
/// Throw an exception for this call made with any arguments.
/// </summary>
/// <param name="value"></param>
/// <param name="ex">Exception to throw</param>
/// <returns></returns>
public static ConfiguredCall ThrowsAsyncForAnyArgs(this Task value, Exception ex) =>
value.ReturnsForAnyArgs(_ => Task.FromException(ex));

/// <summary>
/// Throw an exception for this call made with any arguments.
/// </summary>
/// <param name="value"></param>
/// <param name="ex">Exception to throw</param>
/// <returns></returns>
public static ConfiguredCall ThrowsAsyncForAnyArgs<T>(this Task<T> value, Exception ex) =>
value.ReturnsForAnyArgs(_ => Task.FromException<T>(ex));

/// <summary>
/// Throws an exception of the given type for this call made with any arguments.
/// </summary>
/// <typeparam name="TException">Type of exception to throw</typeparam>
/// <param name="value"></param>
/// <returns></returns>
public static ConfiguredCall ThrowsAsyncForAnyArgs<TException>(this Task value)
where TException : notnull, Exception, new()
{
return value.ReturnsForAnyArgs(_ => FromException(value, new TException()));
}

/// <summary>
/// Throws an exception for this call made with any arguments, as generated by the specified function.
/// </summary>
/// <param name="value"></param>
/// <param name="createException">Func creating exception object</param>
/// <returns></returns>
public static ConfiguredCall ThrowsAsyncForAnyArgs(this Task value, Func<CallInfo, Exception> createException) =>
value.ReturnsForAnyArgs(ci => Task.FromException(createException(ci)));

/// <summary>
/// Throws an exception for this call made with any arguments, as generated by the specified function.
/// </summary>
/// <param name="value"></param>
/// <param name="createException">Func creating exception object</param>
/// <returns></returns>
public static ConfiguredCall ThrowsAsyncForAnyArgs<T>(this Task<T> value, Func<CallInfo, Exception> createException) =>
value.ReturnsForAnyArgs(ci => Task.FromException<T>(createException(ci)));

#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER
/// <summary>
/// Throw an exception for this call.
/// </summary>
/// <param name="value"></param>
/// <param name="ex">Exception to throw</param>
/// <returns></returns>
public static ConfiguredCall ThrowsAsync<T>(this ValueTask<T> value, Exception ex) =>
value.Returns(_ => ValueTask.FromException<T>(ex));

/// <summary>
/// Throw an exception of the given type for this call.
/// </summary>
/// <typeparam name="TResult">Type of exception to throw</typeparam>
/// <typeparam name="TException">Type of exception to throw</typeparam>
/// <param name="value"></param>
/// <returns></returns>
public static ConfiguredCall ThrowsAsync<TResult, TException>(this ValueTask<TResult> value)
where TException : notnull, Exception, new()
{
return value.Returns(_ => ValueTask.FromException<TResult>(new TException()));
}

/// <summary>
/// Throw an exception for this call, as generated by the specified function.
/// </summary>
/// <param name="value"></param>
/// <param name="createException">Func creating exception object</param>
/// <returns></returns>
public static ConfiguredCall ThrowsAsync<T>(this ValueTask<T> value, Func<CallInfo, Exception> createException) =>
value.Returns(ci => ValueTask.FromException<T>(createException(ci)));

/// <summary>
/// Throws an exception of the given type for this call made with any arguments.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TException">Type of exception to throw</typeparam>
/// <param name="value"></param>
/// <returns></returns>
public static ConfiguredCall ThrowsAsyncForAnyArgs<T, TException>(this ValueTask<T> value)
where TException : notnull, Exception, new()
{
return value.ReturnsForAnyArgs(_ => ValueTask.FromException<T>(new TException()));
}

/// <summary>
/// Throw an exception for this call made with any arguments.
/// </summary>
/// <param name="value"></param>
/// <param name="ex">Exception to throw</param>
/// <returns></returns>
public static ConfiguredCall ThrowsAsyncForAnyArgs<T>(this ValueTask<T> value, Exception ex) =>
value.ReturnsForAnyArgs(_ => ValueTask.FromException<T>(ex));

/// <summary>
/// Throws an exception for this call made with any arguments, as generated by the specified function.
/// </summary>
/// <param name="value"></param>
/// <param name="createException">Func creating exception object</param>
/// <returns></returns>
public static ConfiguredCall ThrowsAsyncForAnyArgs<T>(this ValueTask<T> value, Func<CallInfo, Exception> createException) =>
value.ReturnsForAnyArgs(ci => ValueTask.FromException<T>(createException(ci)));
#endif

private static object FromException(object value, Exception exception)
{
// Handle Task<T>
var valueType = value.GetType();
if (valueType.IsConstructedGenericType)
{
var fromExceptionMethodInfo = typeof(Task).GetMethods(BindingFlags.Static | BindingFlags.Public).Single(m => m.Name == "FromException" && m.ContainsGenericParameters);
var specificFromExceptionMethod = fromExceptionMethodInfo.MakeGenericMethod(valueType.GenericTypeArguments);
return specificFromExceptionMethod.Invoke(null, new object[] {exception});
}

return Task.FromException(exception);
}
#endif
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,17 @@ public interface ISomething

object this[string key] { get; set; }
System.Threading.Tasks.Task Async();
System.Threading.Tasks.Task DoAsync(object stuff);
System.Threading.Tasks.Task<int> CountAsync();
System.Threading.Tasks.Task<int> AnythingAsync(object stuff);
System.Threading.Tasks.Task<string> EchoAsync(int i);
System.Threading.Tasks.Task<string> SayAsync(string s);
System.Threading.Tasks.Task<SomeClass> SomeActionAsync();
System.Threading.Tasks.Task<SomeClass> SomeActionWithParamsAsync(int i, string s);

System.Threading.Tasks.ValueTask<int> CountValueTaskAsync();
System.Threading.Tasks.ValueTask<string> EchoValueTaskAsync(int i);
System.Threading.Tasks.ValueTask<int> AnythingValueTaskAsync(object stuff);
System.Threading.Tasks.ValueTask<string> SayValueTaskAsync(string s);
System.Threading.Tasks.ValueTask<SomeClass> SomeActionValueTaskAsync();
System.Threading.Tasks.ValueTask<SomeClass> SomeActionWithParamsValueTaskAsync(int i, string s);
Expand Down
Loading

0 comments on commit f84efda

Please sign in to comment.