From 7af4da5964a9947c0a181d9a0adb84b4fd54d52f Mon Sep 17 00:00:00 2001 From: Marc Selman Date: Wed, 5 Feb 2025 15:02:20 +0100 Subject: [PATCH 1/2] Added exception extensions for ValueTask without return type --- .../Extensions/ExceptionExtensions.cs | 60 ++++++++++++ .../Infrastructure/ISomething.cs | 2 + .../ThrowingAsyncExceptions.cs | 98 +++++++++++++++++++ 3 files changed, 160 insertions(+) diff --git a/src/NSubstitute/Extensions/ExceptionExtensions.cs b/src/NSubstitute/Extensions/ExceptionExtensions.cs index 43636b67..41968ce1 100644 --- a/src/NSubstitute/Extensions/ExceptionExtensions.cs +++ b/src/NSubstitute/Extensions/ExceptionExtensions.cs @@ -223,6 +223,66 @@ public static ConfiguredCall ThrowsAsyncForAnyArgs(this ValueTask value, E /// public static ConfiguredCall ThrowsAsyncForAnyArgs(this ValueTask value, Func createException) => value.ReturnsForAnyArgs(ci => ValueTask.FromException(createException(ci))); + + /// + /// Throw an exception for this call. + /// + /// + /// Exception to throw + /// + public static ConfiguredCall ThrowsAsync(this ValueTask value, Exception ex) => + value.Returns(_ => ValueTask.FromException(ex)); + + /// + /// Throw an exception of the given type for this call. + /// + /// Type of exception to throw + /// + /// + public static ConfiguredCall ThrowsAsync(this ValueTask value) + where TException : notnull, Exception, new() + { + return value.Returns(_ => ValueTask.FromException(new TException())); + } + + /// + /// Throw an exception for this call, as generated by the specified function. + /// + /// + /// Func creating exception object + /// + public static ConfiguredCall ThrowsAsync(this ValueTask value, Func createException) => + value.Returns(ci => ValueTask.FromException(createException(ci))); + + /// + /// Throws an exception of the given type for this call made with any arguments. + /// + /// Type of exception to throw + /// + /// + public static ConfiguredCall ThrowsAsyncForAnyArgs(this ValueTask value) + where TException : notnull, Exception, new() + { + return value.ReturnsForAnyArgs(_ => ValueTask.FromException(new TException())); + } + + /// + /// Throw an exception for this call made with any arguments. + /// + /// + /// Exception to throw + /// + public static ConfiguredCall ThrowsAsyncForAnyArgs(this ValueTask value, Exception ex) => + value.ReturnsForAnyArgs(_ => ValueTask.FromException(ex)); + + /// + /// Throws an exception for this call made with any arguments, as generated by the specified function. + /// + /// + /// Func creating exception object + /// + public static ConfiguredCall ThrowsAsyncForAnyArgs(this ValueTask value, Func createException) => + value.ReturnsForAnyArgs(ci => ValueTask.FromException(createException(ci))); #endif private static object FromException(object value, Exception exception) diff --git a/tests/NSubstitute.Acceptance.Specs/Infrastructure/ISomething.cs b/tests/NSubstitute.Acceptance.Specs/Infrastructure/ISomething.cs index f4aa76f1..5e5b007a 100644 --- a/tests/NSubstitute.Acceptance.Specs/Infrastructure/ISomething.cs +++ b/tests/NSubstitute.Acceptance.Specs/Infrastructure/ISomething.cs @@ -31,8 +31,10 @@ public interface ISomething Task NullableCountAsync(); Task NullableWithParamsAsync(int i, string s); + ValueTask VoidValueTaskAsync(); ValueTask CountValueTaskAsync(); ValueTask EchoValueTaskAsync(int i); + ValueTask AnythingVoidValueTaskAsync(object stuff); ValueTask AnythingValueTaskAsync(object stuff); ValueTask SayValueTaskAsync(string s); ValueTask SomeActionValueTaskAsync(); diff --git a/tests/NSubstitute.Acceptance.Specs/ThrowingAsyncExceptions.cs b/tests/NSubstitute.Acceptance.Specs/ThrowingAsyncExceptions.cs index 29fe4e11..62b5e73e 100644 --- a/tests/NSubstitute.Acceptance.Specs/ThrowingAsyncExceptions.cs +++ b/tests/NSubstitute.Acceptance.Specs/ThrowingAsyncExceptions.cs @@ -295,6 +295,88 @@ public void ThrowExceptionCreatedByFactoryFuncForAnyArgs() AssertFaultedTaskException(() => _something.AnythingValueTaskAsync(new object())); } + [Test] + public void ThrowVoidAsyncException() + { + var exception = new Exception(); + _something.VoidValueTaskAsync().ThrowsAsync(exception); + + AssertFaultedTaskException(() => _something.VoidValueTaskAsync()); + } + + [Test] + public void ThrowVoidAsyncExceptionWithDefaultConstructor() + { + _something.VoidValueTaskAsync().ThrowsAsync(); + + AssertFaultedTaskException(() => _something.VoidValueTaskAsync()); + } + + [Test] + public void ThrowVoidExceptionWithMessage() + { + const string exceptionMessage = "This is exception's message"; + + _something.VoidValueTaskAsync().ThrowsAsync(new Exception(exceptionMessage)); + + Exception exceptionThrown = AssertFaultedTaskException(() => _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(() => _something.VoidValueTaskAsync()); + + ClassicAssert.IsNotNull(exceptionThrown.InnerException); + ClassicAssert.IsInstanceOf(exceptionThrown.InnerException); + } + + [Test] + public void ThrowVoidExceptionUsingFactoryFunc() + { + _something.AnythingVoidValueTaskAsync("abc").ThrowsAsync(ci => new ArgumentException("Args:" + ci.Args()[0])); + + AssertFaultedTaskException(() => _something.AnythingVoidValueTaskAsync("abc")); + } + + [Test] + public void DoesNotThrowVoidForNonMatchingArgs() + { + _something.AnythingVoidValueTaskAsync(12).ThrowsAsync(new Exception()); + + AssertFaultedTaskException(() => _something.AnythingVoidValueTaskAsync(12)); + AssertDoesNotThrow(() => _something.AnythingVoidValueTaskAsync(11)); + } + + [Test] + public void ThrowVoidExceptionForAnyArgs() + { + _something.AnythingVoidValueTaskAsync(12).ThrowsAsyncForAnyArgs(new Exception()); + + AssertFaultedTaskException(() => _something.AnythingVoidValueTaskAsync(null)); + AssertFaultedTaskException(() => _something.AnythingVoidValueTaskAsync(12)); + } + + [Test] + public void ThrowVoidExceptionWithDefaultConstructorForAnyArgs() + { + _something.AnythingVoidValueTaskAsync(12).ThrowsAsyncForAnyArgs(); + + AssertFaultedTaskException(() => _something.AnythingVoidValueTaskAsync(null)); + } + + [Test] + public void ThrowVoidExceptionCreatedByFactoryFuncForAnyArgs() + { + _something.AnythingVoidValueTaskAsync(null).ThrowsAsyncForAnyArgs(ci => new ArgumentException("Args:" + ci.Args()[0])); + + AssertFaultedTaskException(() => _something.AnythingVoidValueTaskAsync(new object())); + } + [SetUp] public void SetUp() { @@ -322,6 +404,22 @@ public static void AssertDoesNotThrow(Func> act) Assert.That(actual.IsFaulted, Is.False); } + + public static TException AssertFaultedTaskException(Func act) + where TException : Exception + { + var actual = act(); + + Assert.That(actual.IsFaulted, Is.True); + return Assert.CatchAsync(async () => await actual); + } + + public static void AssertDoesNotThrow(Func act) + { + var actual = act(); + + Assert.That(actual.IsFaulted, Is.False); + } } #endif From b2b3f27c2d4acf0c0299d7c782d723d0970adf29 Mon Sep 17 00:00:00 2001 From: Marc Selman Date: Mon, 24 Mar 2025 09:59:25 +0100 Subject: [PATCH 2/2] Fix formatting --- src/NSubstitute/Core/WhenCalled.cs | 16 ++++++++-------- src/NSubstitute/NSubstitute.csproj | 8 ++++---- .../Routing/Handlers/RaiseEventHandler.cs | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/NSubstitute/Core/WhenCalled.cs b/src/NSubstitute/Core/WhenCalled.cs index 8d956f60..b22ab29a 100644 --- a/src/NSubstitute/Core/WhenCalled.cs +++ b/src/NSubstitute/Core/WhenCalled.cs @@ -20,15 +20,15 @@ public void Do(Action callbackWithArguments) } /// - /// Perform this action when called. - /// - /// - public void Do(Func callbackWithArguments) - { - Do(callInfo => callbackWithArguments(callInfo).GetAwaiter().GetResult()); - } + /// Perform this action when called. + /// + /// + public void Do(Func callbackWithArguments) + { + Do(callInfo => callbackWithArguments(callInfo).GetAwaiter().GetResult()); + } - /// + /// /// Perform this configured callback when called. /// /// diff --git a/src/NSubstitute/NSubstitute.csproj b/src/NSubstitute/NSubstitute.csproj index 3df5a5c3..381eb118 100644 --- a/src/NSubstitute/NSubstitute.csproj +++ b/src/NSubstitute/NSubstitute.csproj @@ -21,8 +21,8 @@ icon.png https://nsubstitute.github.io/ BSD-3-Clause - https://github.com/nsubstitute/NSubstitute - git + https://github.com/nsubstitute/NSubstitute + git @@ -46,7 +46,7 @@ - + @@ -54,5 +54,5 @@ - + diff --git a/src/NSubstitute/Routing/Handlers/RaiseEventHandler.cs b/src/NSubstitute/Routing/Handlers/RaiseEventHandler.cs index 76988cfd..f4cdb805 100644 --- a/src/NSubstitute/Routing/Handlers/RaiseEventHandler.cs +++ b/src/NSubstitute/Routing/Handlers/RaiseEventHandler.cs @@ -27,7 +27,7 @@ public RouteAction Handle(ICall call) try { - (handler.DynamicInvoke(eventArguments) as Task)?.GetAwaiter().GetResult(); + (handler.DynamicInvoke(eventArguments) as Task)?.GetAwaiter().GetResult(); } catch (TargetInvocationException e) {