From b3a3556baa78e9c7bd9604505979491fa2432699 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20St=C3=BChmer?= Date: Thu, 29 May 2025 19:37:06 +0200 Subject: [PATCH] chore: Extended `Assert.Throw` and `Assert.ThrowAsync` with ParameterName Overload Closed: #2470 --- TUnit.Assertions/Assert.cs | 81 ++++++++++++++----- ..._Has_No_API_Changes.DotNet2_0.verified.txt | 8 ++ ..._Has_No_API_Changes.DotNet8_0.verified.txt | 8 ++ ..._Has_No_API_Changes.DotNet9_0.verified.txt | 8 ++ 4 files changed, 83 insertions(+), 22 deletions(-) diff --git a/TUnit.Assertions/Assert.cs b/TUnit.Assertions/Assert.cs index 5c19abcbf9..0338f45cd8 100644 --- a/TUnit.Assertions/Assert.cs +++ b/TUnit.Assertions/Assert.cs @@ -3,6 +3,7 @@ using System.Runtime.CompilerServices; using TUnit.Assertions.AssertionBuilders; using TUnit.Assertions.Extensions; +using TUnit.Assertions.Helpers; using TUnit.Assertions.Wrappers; namespace TUnit.Assertions; @@ -13,47 +14,47 @@ public static ValueAssertionBuilder That(TActual value, [Calle { return new ValueAssertionBuilder(value, doNotPopulateThisValue); } - + public static ValueAssertionBuilder> That(IEnumerable enumerable, [CallerArgumentExpression(nameof(enumerable))] string? doNotPopulateThisValue = null) { return new ValueAssertionBuilder>(new UnTypedEnumerableWrapper(enumerable), doNotPopulateThisValue); } - + public static DelegateAssertionBuilder That(Action value, [CallerArgumentExpression(nameof(value))] string? doNotPopulateThisValue = null) { return new DelegateAssertionBuilder(value, doNotPopulateThisValue); } - + public static ValueDelegateAssertionBuilder That(Func value, [CallerArgumentExpression(nameof(value))] string? doNotPopulateThisValue = null) { return new ValueDelegateAssertionBuilder(value, doNotPopulateThisValue); } - + public static AsyncDelegateAssertionBuilder That(Func value, [CallerArgumentExpression(nameof(value))] string? doNotPopulateThisValue = null) { return new AsyncDelegateAssertionBuilder(value, doNotPopulateThisValue); } - + public static AsyncValueDelegateAssertionBuilder That(Func> value, [CallerArgumentExpression(nameof(value))] string? doNotPopulateThisValue = null) { return new AsyncValueDelegateAssertionBuilder(value, doNotPopulateThisValue); } - + public static AsyncDelegateAssertionBuilder That(Task value, [CallerArgumentExpression(nameof(value))] string? doNotPopulateThisValue = null) { return new AsyncDelegateAssertionBuilder(async () => await value, doNotPopulateThisValue); } - + public static AsyncValueDelegateAssertionBuilder That(Task value, [CallerArgumentExpression(nameof(value))] string? doNotPopulateThisValue = null) { return new AsyncValueDelegateAssertionBuilder(async () => await value, doNotPopulateThisValue); } - + public static AsyncDelegateAssertionBuilder That(ValueTask value, [CallerArgumentExpression(nameof(value))] string? doNotPopulateThisValue = null) { return new AsyncDelegateAssertionBuilder(async () => await value, doNotPopulateThisValue); } - + public static AsyncValueDelegateAssertionBuilder That(ValueTask value, [CallerArgumentExpression(nameof(value))] string? doNotPopulateThisValue = null) { return new AsyncValueDelegateAssertionBuilder(async () => await value, doNotPopulateThisValue); @@ -71,32 +72,56 @@ public static Task ThrowsAsync(Func @delegate, public static Task ThrowsAsync(Task @delegate, [CallerArgumentExpression(nameof(@delegate))] string? doNotPopulateThisValue = null) => ThrowsAsync(async () => await @delegate, doNotPopulateThisValue); - + public static Task ThrowsAsync(ValueTask @delegate, [CallerArgumentExpression(nameof(@delegate))] string? doNotPopulateThisValue = null) => ThrowsAsync(async () => await @delegate, doNotPopulateThisValue); - + public static Task ThrowsAsync(Task @delegate, [CallerArgumentExpression(nameof(@delegate))] string? doNotPopulateThisValue = null) where TException : Exception => ThrowsAsync(async () => await @delegate, doNotPopulateThisValue); - + public static Task ThrowsAsync(ValueTask @delegate, [CallerArgumentExpression(nameof(@delegate))] string? doNotPopulateThisValue = null) where TException : Exception => ThrowsAsync(async () => await @delegate, doNotPopulateThisValue); - + public static async Task ThrowsAsync(Func @delegate, [CallerArgumentExpression(nameof(@delegate))] string? doNotPopulateThisValue = null) where TException : Exception { - return (TException) await ThrowsAsync(typeof(TException), @delegate, doNotPopulateThisValue); + return (TException)await ThrowsAsync(typeof(TException), @delegate, doNotPopulateThisValue); + } + + public static Task ThrowsAsync(string parameterName, + Task @delegate, + [CallerArgumentExpression(nameof(@delegate))] string? doNotPopulateThisValue = null) where TException : ArgumentException + => ThrowsAsync(parameterName, async () => await @delegate, doNotPopulateThisValue); + + public static Task ThrowsAsync(string parameterName, + ValueTask @delegate, + [CallerArgumentExpression(nameof(@delegate))] string? doNotPopulateThisValue = null) where TException : ArgumentException + => ThrowsAsync(parameterName, async () => await @delegate, doNotPopulateThisValue); + + public static async Task ThrowsAsync(string parameterName, + Func @delegate, + [CallerArgumentExpression(nameof(@delegate))] string? doNotPopulateThisValue = null) where TException : ArgumentException + { + var ex = (TException)await ThrowsAsync(typeof(TException), @delegate, doNotPopulateThisValue); + + if (ex.ParamName?.Equals(parameterName, StringComparison.Ordinal) == false) + { + Fail($"Incorrect parameter name {new StringDifference(ex.ParamName, parameterName).ToString("it differs at index")}"); + } + + return ex; } - + public static Task ThrowsAsync(Type type, Task @delegate, [CallerArgumentExpression(nameof(@delegate))] string? doNotPopulateThisValue = null) => ThrowsAsync(type, async () => await @delegate, doNotPopulateThisValue); - + public static Task ThrowsAsync(Type type, ValueTask @delegate, [CallerArgumentExpression(nameof(@delegate))] string? doNotPopulateThisValue = null) => ThrowsAsync(type, async () => await @delegate, doNotPopulateThisValue); - + public static async Task ThrowsAsync(Type type, Func @delegate, [CallerArgumentExpression(nameof(@delegate))] string? doNotPopulateThisValue = null) { try @@ -113,14 +138,14 @@ public static async Task ThrowsAsync(Type type, Func @delegate, } Fail($"No exception was thrown by {doNotPopulateThisValue.GetStringOr("the delegate")}"); - + return null; } public static Exception Throws(Action @delegate, [CallerArgumentExpression(nameof(@delegate))] string? doNotPopulateThisValue = null) => Throws(@delegate, doNotPopulateThisValue); - + public static Exception Throws(Type type, Action @delegate, [CallerArgumentExpression(nameof(@delegate))] string? doNotPopulateThisValue = null) { try @@ -135,15 +160,27 @@ public static Exception Throws(Type type, Action @delegate, [CallerArgumentExpre { Fail($"Exception is of type {e.GetType().Name} instead of {type.Name} for {doNotPopulateThisValue.GetStringOr("the delegate")}"); } - + Fail($"No exception was thrown by {doNotPopulateThisValue.GetStringOr("the delegate")}"); return null; } - + public static TException Throws(Action @delegate, [CallerArgumentExpression(nameof(@delegate))] string? doNotPopulateThisValue = null) where TException : Exception { - return (TException) Throws(typeof(TException), @delegate, doNotPopulateThisValue); + return (TException)Throws(typeof(TException), @delegate, doNotPopulateThisValue); + } + + public static TException Throws(string parameterName, Action @delegate, [CallerArgumentExpression(nameof(@delegate))] string? doNotPopulateThisValue = null) where TException : ArgumentException + { + var ex = Throws(@delegate, doNotPopulateThisValue); + + if (ex.ParamName?.Equals(parameterName, StringComparison.Ordinal) == false) + { + Fail($"Incorrect parameter name {new StringDifference(ex.ParamName, parameterName).ToString("it differs at index")}"); + } + + return ex; } [DoesNotReturn] diff --git a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet2_0.verified.txt b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet2_0.verified.txt index 6ed2801cbb..f70539838b 100644 --- a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet2_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet2_0.verified.txt @@ -20,6 +20,8 @@ namespace TUnit.Assertions public static System.Exception Throws(System.Type type, System.Action @delegate, [System.Runtime.CompilerServices.CallerArgumentExpression("delegate")] string? doNotPopulateThisValue = null) { } public static TException Throws(System.Action @delegate, [System.Runtime.CompilerServices.CallerArgumentExpression("delegate")] string? doNotPopulateThisValue = null) where TException : System.Exception { } + public static TException Throws(string parameterName, System.Action @delegate, [System.Runtime.CompilerServices.CallerArgumentExpression("delegate")] string? doNotPopulateThisValue = null) + where TException : System.ArgumentException { } public static System.Threading.Tasks.Task ThrowsAsync(System.Func @delegate, [System.Runtime.CompilerServices.CallerArgumentExpression("delegate")] string? doNotPopulateThisValue = null) { } public static System.Threading.Tasks.Task ThrowsAsync(System.Threading.Tasks.Task @delegate, [System.Runtime.CompilerServices.CallerArgumentExpression("delegate")] string? doNotPopulateThisValue = null) { } public static System.Threading.Tasks.Task ThrowsAsync(System.Threading.Tasks.ValueTask @delegate, [System.Runtime.CompilerServices.CallerArgumentExpression("delegate")] string? doNotPopulateThisValue = null) { } @@ -32,6 +34,12 @@ namespace TUnit.Assertions where TException : System.Exception { } public static System.Threading.Tasks.Task ThrowsAsync(System.Threading.Tasks.ValueTask @delegate, [System.Runtime.CompilerServices.CallerArgumentExpression("delegate")] string? doNotPopulateThisValue = null) where TException : System.Exception { } + public static System.Threading.Tasks.Task ThrowsAsync(string parameterName, System.Func @delegate, [System.Runtime.CompilerServices.CallerArgumentExpression("delegate")] string? doNotPopulateThisValue = null) + where TException : System.ArgumentException { } + public static System.Threading.Tasks.Task ThrowsAsync(string parameterName, System.Threading.Tasks.Task @delegate, [System.Runtime.CompilerServices.CallerArgumentExpression("delegate")] string? doNotPopulateThisValue = null) + where TException : System.ArgumentException { } + public static System.Threading.Tasks.Task ThrowsAsync(string parameterName, System.Threading.Tasks.ValueTask @delegate, [System.Runtime.CompilerServices.CallerArgumentExpression("delegate")] string? doNotPopulateThisValue = null) + where TException : System.ArgumentException { } } public readonly struct AssertionData : System.IEquatable { diff --git a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet8_0.verified.txt b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet8_0.verified.txt index 9f76c9809f..5b9367ef1d 100644 --- a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet8_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet8_0.verified.txt @@ -20,6 +20,8 @@ namespace TUnit.Assertions public static System.Exception Throws(System.Type type, System.Action @delegate, [System.Runtime.CompilerServices.CallerArgumentExpression("delegate")] string? doNotPopulateThisValue = null) { } public static TException Throws(System.Action @delegate, [System.Runtime.CompilerServices.CallerArgumentExpression("delegate")] string? doNotPopulateThisValue = null) where TException : System.Exception { } + public static TException Throws(string parameterName, System.Action @delegate, [System.Runtime.CompilerServices.CallerArgumentExpression("delegate")] string? doNotPopulateThisValue = null) + where TException : System.ArgumentException { } public static System.Threading.Tasks.Task ThrowsAsync(System.Func @delegate, [System.Runtime.CompilerServices.CallerArgumentExpression("delegate")] string? doNotPopulateThisValue = null) { } public static System.Threading.Tasks.Task ThrowsAsync(System.Threading.Tasks.Task @delegate, [System.Runtime.CompilerServices.CallerArgumentExpression("delegate")] string? doNotPopulateThisValue = null) { } public static System.Threading.Tasks.Task ThrowsAsync(System.Threading.Tasks.ValueTask @delegate, [System.Runtime.CompilerServices.CallerArgumentExpression("delegate")] string? doNotPopulateThisValue = null) { } @@ -32,6 +34,12 @@ namespace TUnit.Assertions where TException : System.Exception { } public static System.Threading.Tasks.Task ThrowsAsync(System.Threading.Tasks.ValueTask @delegate, [System.Runtime.CompilerServices.CallerArgumentExpression("delegate")] string? doNotPopulateThisValue = null) where TException : System.Exception { } + public static System.Threading.Tasks.Task ThrowsAsync(string parameterName, System.Func @delegate, [System.Runtime.CompilerServices.CallerArgumentExpression("delegate")] string? doNotPopulateThisValue = null) + where TException : System.ArgumentException { } + public static System.Threading.Tasks.Task ThrowsAsync(string parameterName, System.Threading.Tasks.Task @delegate, [System.Runtime.CompilerServices.CallerArgumentExpression("delegate")] string? doNotPopulateThisValue = null) + where TException : System.ArgumentException { } + public static System.Threading.Tasks.Task ThrowsAsync(string parameterName, System.Threading.Tasks.ValueTask @delegate, [System.Runtime.CompilerServices.CallerArgumentExpression("delegate")] string? doNotPopulateThisValue = null) + where TException : System.ArgumentException { } } public readonly struct AssertionData : System.IEquatable { diff --git a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet9_0.verified.txt b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet9_0.verified.txt index 65ddabb54f..7279056b26 100644 --- a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet9_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet9_0.verified.txt @@ -20,6 +20,8 @@ namespace TUnit.Assertions public static System.Exception Throws(System.Type type, System.Action @delegate, [System.Runtime.CompilerServices.CallerArgumentExpression("delegate")] string? doNotPopulateThisValue = null) { } public static TException Throws(System.Action @delegate, [System.Runtime.CompilerServices.CallerArgumentExpression("delegate")] string? doNotPopulateThisValue = null) where TException : System.Exception { } + public static TException Throws(string parameterName, System.Action @delegate, [System.Runtime.CompilerServices.CallerArgumentExpression("delegate")] string? doNotPopulateThisValue = null) + where TException : System.ArgumentException { } public static System.Threading.Tasks.Task ThrowsAsync(System.Func @delegate, [System.Runtime.CompilerServices.CallerArgumentExpression("delegate")] string? doNotPopulateThisValue = null) { } public static System.Threading.Tasks.Task ThrowsAsync(System.Threading.Tasks.Task @delegate, [System.Runtime.CompilerServices.CallerArgumentExpression("delegate")] string? doNotPopulateThisValue = null) { } public static System.Threading.Tasks.Task ThrowsAsync(System.Threading.Tasks.ValueTask @delegate, [System.Runtime.CompilerServices.CallerArgumentExpression("delegate")] string? doNotPopulateThisValue = null) { } @@ -32,6 +34,12 @@ namespace TUnit.Assertions where TException : System.Exception { } public static System.Threading.Tasks.Task ThrowsAsync(System.Threading.Tasks.ValueTask @delegate, [System.Runtime.CompilerServices.CallerArgumentExpression("delegate")] string? doNotPopulateThisValue = null) where TException : System.Exception { } + public static System.Threading.Tasks.Task ThrowsAsync(string parameterName, System.Func @delegate, [System.Runtime.CompilerServices.CallerArgumentExpression("delegate")] string? doNotPopulateThisValue = null) + where TException : System.ArgumentException { } + public static System.Threading.Tasks.Task ThrowsAsync(string parameterName, System.Threading.Tasks.Task @delegate, [System.Runtime.CompilerServices.CallerArgumentExpression("delegate")] string? doNotPopulateThisValue = null) + where TException : System.ArgumentException { } + public static System.Threading.Tasks.Task ThrowsAsync(string parameterName, System.Threading.Tasks.ValueTask @delegate, [System.Runtime.CompilerServices.CallerArgumentExpression("delegate")] string? doNotPopulateThisValue = null) + where TException : System.ArgumentException { } } public readonly struct AssertionData : System.IEquatable {