Skip to content

Commit

Permalink
Added exception extensions for ValueTask without return type
Browse files Browse the repository at this point in the history
  • Loading branch information
Marc Selman committed Feb 5, 2025
1 parent 4122187 commit 58a6290
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 0 deletions.
60 changes: 60 additions & 0 deletions src/NSubstitute/Extensions/ExceptionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,66 @@ public static ConfiguredCall ThrowsAsyncForAnyArgs<T>(this ValueTask<T> value, E
/// <returns></returns>
public static ConfiguredCall ThrowsAsyncForAnyArgs<T>(this ValueTask<T> value, Func<CallInfo, Exception> createException) =>
value.ReturnsForAnyArgs(ci => ValueTask.FromException<T>(createException(ci)));

/// <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 ValueTask value, Exception ex) =>
value.Returns(_ => ValueTask.FromException(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 ValueTask value)
where TException : notnull, Exception, new()
{
return value.Returns(_ => ValueTask.FromException(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 ValueTask value, Func<CallInfo, Exception> createException) =>
value.Returns(ci => ValueTask.FromException(createException(ci)));

/// <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 ValueTask value)
where TException : notnull, Exception, new()
{
return value.ReturnsForAnyArgs(_ => ValueTask.FromException(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(this ValueTask value, Exception ex) =>
value.ReturnsForAnyArgs(_ => ValueTask.FromException(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(this ValueTask value, Func<CallInfo, Exception> createException) =>
value.ReturnsForAnyArgs(ci => ValueTask.FromException(createException(ci)));
#endif

private static object FromException(object value, Exception exception)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ public interface ISomething
Task<int?> NullableCountAsync();
Task<int?> NullableWithParamsAsync(int i, string s);

ValueTask VoidValueTaskAsync();
ValueTask<int> CountValueTaskAsync();
ValueTask<string> EchoValueTaskAsync(int i);
ValueTask AnythingVoidValueTaskAsync(object stuff);
ValueTask<int> AnythingValueTaskAsync(object stuff);
ValueTask<string> SayValueTaskAsync(string s);
ValueTask<SomeClass> SomeActionValueTaskAsync();
Expand Down
98 changes: 98 additions & 0 deletions tests/NSubstitute.Acceptance.Specs/ThrowingAsyncExceptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,88 @@ public void ThrowExceptionCreatedByFactoryFuncForAnyArgs()
AssertFaultedTaskException<int, ArgumentException>(() => _something.AnythingValueTaskAsync(new object()));
}

[Test]
public void ThrowVoidAsyncException()
{
var exception = new Exception();
_something.VoidValueTaskAsync().ThrowsAsync(exception);

AssertFaultedTaskException<Exception>(() => _something.VoidValueTaskAsync());
}

[Test]
public void ThrowVoidAsyncExceptionWithDefaultConstructor()
{
_something.VoidValueTaskAsync().ThrowsAsync<ArgumentException>();

AssertFaultedTaskException<ArgumentException>(() => _something.VoidValueTaskAsync());
}

[Test]
public void ThrowVoidExceptionWithMessage()
{
const string exceptionMessage = "This is exception's message";

_something.VoidValueTaskAsync().ThrowsAsync(new Exception(exceptionMessage));

Exception exceptionThrown = AssertFaultedTaskException<Exception>(() => _something.VoidValueTaskAsync());
ClassicAssert.AreEqual(exceptionMessage, exceptionThrown.Message);
}

[Test]
public void ThrowVoidExceptionWithInnerException()
{
ArgumentException innerException = new ArgumentException();
_something.VoidValueTaskAsync().ThrowsAsync(new Exception("Exception message", innerException));

Exception exceptionThrown = AssertFaultedTaskException<Exception>(() => _something.VoidValueTaskAsync());

ClassicAssert.IsNotNull(exceptionThrown.InnerException);
ClassicAssert.IsInstanceOf<ArgumentException>(exceptionThrown.InnerException);
}

[Test]
public void ThrowVoidExceptionUsingFactoryFunc()
{
_something.AnythingVoidValueTaskAsync("abc").ThrowsAsync(ci => new ArgumentException("Args:" + ci.Args()[0]));

AssertFaultedTaskException<ArgumentException>(() => _something.AnythingVoidValueTaskAsync("abc"));
}

[Test]
public void DoesNotThrowVoidForNonMatchingArgs()
{
_something.AnythingVoidValueTaskAsync(12).ThrowsAsync(new Exception());

AssertFaultedTaskException<Exception>(() => _something.AnythingVoidValueTaskAsync(12));
AssertDoesNotThrow(() => _something.AnythingVoidValueTaskAsync(11));
}

[Test]
public void ThrowVoidExceptionForAnyArgs()
{
_something.AnythingVoidValueTaskAsync(12).ThrowsAsyncForAnyArgs(new Exception());

AssertFaultedTaskException<Exception>(() => _something.AnythingVoidValueTaskAsync(null));
AssertFaultedTaskException<Exception>(() => _something.AnythingVoidValueTaskAsync(12));
}

[Test]
public void ThrowVoidExceptionWithDefaultConstructorForAnyArgs()
{
_something.AnythingVoidValueTaskAsync(12).ThrowsAsyncForAnyArgs<InvalidOperationException>();

AssertFaultedTaskException<InvalidOperationException>(() => _something.AnythingVoidValueTaskAsync(null));
}

[Test]
public void ThrowVoidExceptionCreatedByFactoryFuncForAnyArgs()
{
_something.AnythingVoidValueTaskAsync(null).ThrowsAsyncForAnyArgs(ci => new ArgumentException("Args:" + ci.Args()[0]));

AssertFaultedTaskException<ArgumentException>(() => _something.AnythingVoidValueTaskAsync(new object()));
}

[SetUp]
public void SetUp()
{
Expand Down Expand Up @@ -322,6 +404,22 @@ public static void AssertDoesNotThrow<T>(Func<ValueTask<T>> act)

Assert.That(actual.IsFaulted, Is.False);
}

public static TException AssertFaultedTaskException<TException>(Func<ValueTask> act)
where TException : Exception
{
var actual = act();

Assert.That(actual.IsFaulted, Is.True);
return Assert.CatchAsync<TException>(async () => await actual);
}

public static void AssertDoesNotThrow(Func<ValueTask> act)
{
var actual = act();

Assert.That(actual.IsFaulted, Is.False);
}
}
#endif

Expand Down

0 comments on commit 58a6290

Please sign in to comment.