diff --git a/src/TestFramework/TestFramework/Assertions/Assert.ThrowsException.cs b/src/TestFramework/TestFramework/Assertions/Assert.ThrowsException.cs index 9d88e3dc0a..39658439f9 100644 --- a/src/TestFramework/TestFramework/Assertions/Assert.ThrowsException.cs +++ b/src/TestFramework/TestFramework/Assertions/Assert.ThrowsException.cs @@ -1,8 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System.Diagnostics.CodeAnalysis; +using System.ComponentModel; using System.Globalization; +using System.Runtime.CompilerServices; namespace Microsoft.VisualStudio.TestTools.UnitTesting; @@ -13,6 +14,60 @@ namespace Microsoft.VisualStudio.TestTools.UnitTesting; /// public sealed partial class Assert { + /// + /// Asserts that the delegate throws an exception of type + /// (or derived type) and throws AssertFailedException if code does not throws exception or throws + /// exception of type other than . + /// + /// + /// Delegate to code to be tested and which is expected to throw exception. + /// + /// + /// The message to include in the exception when does not throws exception of type . + /// + /// + /// An array of parameters to use when formatting . + /// + /// + /// The type of exception expected to be thrown. + /// + /// + /// Thrown if does not throws exception of type . + /// + /// + /// The exception that was thrown. + /// + public static TException Throws(Action action, string message = "", params object[] messageArgs) + where TException : Exception + => ThrowsException(action, isStrictType: false, message, parameters: messageArgs); + + /// + /// Asserts that the delegate throws an exception of type + /// (and not of derived type) and throws AssertFailedException if code does not throws exception or throws + /// exception of type other than . + /// + /// + /// Delegate to code to be tested and which is expected to throw exception. + /// + /// + /// The message to include in the exception when does not throws exception of type . + /// + /// + /// An array of parameters to use when formatting . + /// + /// + /// The type of exception expected to be thrown. + /// + /// + /// Thrown if does not throws exception of type . + /// + /// + /// The exception that was thrown. + /// + public static TException ThrowsExactly(Action action, string message = "", params object[] messageArgs) + where TException : Exception + => ThrowsException(action, isStrictType: true, message, parameters: messageArgs); + /// /// Tests whether the code specified by delegate throws exact given exception /// of type (and not of derived type) and throws AssertFailedException @@ -22,7 +77,7 @@ public sealed partial class Assert /// Delegate to code to be tested and which is expected to throw exception. /// /// - /// Type of exception expected to be thrown. + /// The exact type of exception expected to be thrown. /// /// /// Thrown if does not throws exception of type . @@ -30,6 +85,7 @@ public sealed partial class Assert /// /// The exception that was thrown. /// + [EditorBrowsable(EditorBrowsableState.Never)] public static T ThrowsException(Action action) where T : Exception => ThrowsException(action, string.Empty, null); @@ -55,6 +111,7 @@ public static T ThrowsException(Action action) /// /// The exception that was thrown. /// + [EditorBrowsable(EditorBrowsableState.Never)] public static T ThrowsException(Action action, string message) where T : Exception => ThrowsException(action, message, null); @@ -76,6 +133,7 @@ public static T ThrowsException(Action action, string message) /// /// The exception that was thrown. /// + [EditorBrowsable(EditorBrowsableState.Never)] public static T ThrowsException(Func action) where T : Exception => ThrowsException(action, string.Empty, null); @@ -101,6 +159,7 @@ public static T ThrowsException(Func action) /// /// The exception that was thrown. /// + [EditorBrowsable(EditorBrowsableState.Never)] public static T ThrowsException(Func action, string message) where T : Exception => ThrowsException(action, message, null); @@ -129,6 +188,7 @@ public static T ThrowsException(Func action, string message) /// /// The exception that was thrown. /// + [EditorBrowsable(EditorBrowsableState.Never)] public static T ThrowsException(Func action, string message, params object?[]? parameters) where T : Exception #pragma warning disable IDE0053 // Use expression body for lambda expression @@ -160,9 +220,13 @@ public static T ThrowsException(Func action, string message, params /// /// The exception that was thrown. /// - [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Requirement is to handle all kinds of user exceptions and format appropriately.")] + [EditorBrowsable(EditorBrowsableState.Never)] public static T ThrowsException(Action action, string message, params object?[]? parameters) where T : Exception + => ThrowsException(action, isStrictType: true, message, parameters: parameters); + + private static TException ThrowsException(Action action, bool isStrictType, string message, [CallerMemberName] string assertMethodName = "", params object?[]? parameters) + where TException : Exception { Guard.NotNull(action); Guard.NotNull(message); @@ -174,19 +238,22 @@ public static T ThrowsException(Action action, string message, params object? } catch (Exception ex) { - if (!typeof(T).Equals(ex.GetType())) + bool isExceptionOfType = isStrictType + ? typeof(TException) == ex.GetType() + : ex is TException; + if (!isExceptionOfType) { userMessage = BuildUserMessage(message, parameters); finalMessage = string.Format( CultureInfo.CurrentCulture, FrameworkMessages.WrongExceptionThrown, userMessage, - typeof(T), + typeof(TException), ex.GetType()); - ThrowAssertFailed("Assert.ThrowsException", finalMessage); + ThrowAssertFailed("Assert." + assertMethodName, finalMessage); } - return (T)ex; + return (TException)ex; } userMessage = BuildUserMessage(message, parameters); @@ -194,13 +261,67 @@ public static T ThrowsException(Action action, string message, params object? CultureInfo.CurrentCulture, FrameworkMessages.NoExceptionThrown, userMessage, - typeof(T)); - ThrowAssertFailed("Assert.ThrowsException", finalMessage); + typeof(TException)); + ThrowAssertFailed("Assert." + assertMethodName, finalMessage); // This will not hit, but need it for compiler. return null; } + /// + /// Asserts that the delegate throws an exception of type + /// (or derived type) and throws AssertFailedException if code does not throws exception or throws + /// exception of type other than . + /// + /// + /// Delegate to code to be tested and which is expected to throw exception. + /// + /// + /// The message to include in the exception when does not throws exception of type . + /// + /// + /// An array of parameters to use when formatting . + /// + /// + /// The type of exception expected to be thrown. + /// + /// + /// Thrown if does not throws exception of type . + /// + /// + /// The exception that was thrown. + /// + public static Task ThrowsAsync(Func action, string message = "", params object[] messageArgs) + where TException : Exception + => ThrowsExceptionAsync(action, isStrictType: false, message, parameters: messageArgs); + + /// + /// Asserts that the delegate throws an exception of type + /// (and not of derived type) and throws AssertFailedException if code does not throws exception or throws + /// exception of type other than . + /// + /// + /// Delegate to code to be tested and which is expected to throw exception. + /// + /// + /// The message to include in the exception when does not throws exception of type . + /// + /// + /// An array of parameters to use when formatting . + /// + /// + /// The type of exception expected to be thrown. + /// + /// + /// Thrown if does not throws exception of type . + /// + /// + /// The exception that was thrown. + /// + public static Task ThrowsExactlyAsync(Func action, string message = "", params object[] messageArgs) + where TException : Exception + => ThrowsExceptionAsync(action, isStrictType: true, message, parameters: messageArgs); + /// /// Tests whether the code specified by delegate throws exact given exception /// of type (and not of derived type) and throws AssertFailedException @@ -218,6 +339,7 @@ public static T ThrowsException(Action action, string message, params object? /// /// The executing the delegate. /// + [EditorBrowsable(EditorBrowsableState.Never)] public static async Task ThrowsExceptionAsync(Func action) where T : Exception => await ThrowsExceptionAsync(action, string.Empty, null) @@ -240,6 +362,7 @@ public static async Task ThrowsExceptionAsync(Func action) /// /// The executing the delegate. /// + [EditorBrowsable(EditorBrowsableState.Never)] public static async Task ThrowsExceptionAsync(Func action, string message) where T : Exception => await ThrowsExceptionAsync(action, message, null) @@ -265,8 +388,14 @@ public static async Task ThrowsExceptionAsync(Func action, string me /// /// The executing the delegate. /// + [EditorBrowsable(EditorBrowsableState.Never)] public static async Task ThrowsExceptionAsync(Func action, string message, params object?[]? parameters) where T : Exception + => await ThrowsExceptionAsync(action, true, message, parameters: parameters) + .ConfigureAwait(false); + + private static async Task ThrowsExceptionAsync(Func action, bool isStrictType, string message, [CallerMemberName] string assertMethodName = "", params object?[]? parameters) + where TException : Exception { Guard.NotNull(action); Guard.NotNull(message); @@ -278,19 +407,23 @@ public static async Task ThrowsExceptionAsync(Func action, string me } catch (Exception ex) { - if (!typeof(T).Equals(ex.GetType())) + bool isExceptionOfType = isStrictType + ? typeof(TException) == ex.GetType() + : ex is TException; + + if (!isExceptionOfType) { userMessage = BuildUserMessage(message, parameters); finalMessage = string.Format( CultureInfo.CurrentCulture, FrameworkMessages.WrongExceptionThrown, userMessage, - typeof(T), + typeof(TException), ex.GetType()); - ThrowAssertFailed("Assert.ThrowsException", finalMessage); + ThrowAssertFailed("Assert." + assertMethodName, finalMessage); } - return (T)ex; + return (TException)ex; } userMessage = BuildUserMessage(message, parameters); @@ -298,8 +431,8 @@ public static async Task ThrowsExceptionAsync(Func action, string me CultureInfo.CurrentCulture, FrameworkMessages.NoExceptionThrown, userMessage, - typeof(T)); - ThrowAssertFailed("Assert.ThrowsException", finalMessage); + typeof(TException)); + ThrowAssertFailed("Assert." + assertMethodName, finalMessage); // This will not hit, but need it for compiler. return null!; diff --git a/src/TestFramework/TestFramework/PublicAPI/PublicAPI.Unshipped.txt b/src/TestFramework/TestFramework/PublicAPI/PublicAPI.Unshipped.txt index 7dc5c58110..7c1390e0bf 100644 --- a/src/TestFramework/TestFramework/PublicAPI/PublicAPI.Unshipped.txt +++ b/src/TestFramework/TestFramework/PublicAPI/PublicAPI.Unshipped.txt @@ -1 +1,5 @@ #nullable enable +static Microsoft.VisualStudio.TestTools.UnitTesting.Assert.Throws(System.Action! action, string! message = "", params object![]! messageArgs) -> TException! +static Microsoft.VisualStudio.TestTools.UnitTesting.Assert.ThrowsAsync(System.Func! action, string! message = "", params object![]! messageArgs) -> System.Threading.Tasks.Task! +static Microsoft.VisualStudio.TestTools.UnitTesting.Assert.ThrowsExactly(System.Action! action, string! message = "", params object![]! messageArgs) -> TException! +static Microsoft.VisualStudio.TestTools.UnitTesting.Assert.ThrowsExactlyAsync(System.Func! action, string! message = "", params object![]! messageArgs) -> System.Threading.Tasks.Task! diff --git a/test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.ThrowsExceptionTests.cs b/test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.ThrowsExceptionTests.cs index 459c84c165..74aa52e347 100644 --- a/test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.ThrowsExceptionTests.cs +++ b/test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.ThrowsExceptionTests.cs @@ -72,7 +72,7 @@ public void ThrowsExceptionAsyncShouldThrowAssertionOnNoException() Verify(innerException is not null); Verify(typeof(AssertFailedException) == innerException.GetType()); - Verify(innerException.Message.Equals("Assert.ThrowsException failed. Expected exception type: but no exception was thrown. ", StringComparison.Ordinal)); + Verify(innerException.Message.Equals("Assert.ThrowsExceptionAsync failed. Expected exception type: but no exception was thrown. ", StringComparison.Ordinal)); } public void ThrowsExceptionAsyncShouldThrowAssertionOnWrongException() @@ -89,7 +89,7 @@ public void ThrowsExceptionAsyncShouldThrowAssertionOnWrongException() Verify(innerException is not null); Assert.AreEqual(typeof(AssertFailedException), innerException.GetType()); - Verify(innerException.Message.Equals("Assert.ThrowsException failed. Expected exception type:. Actual exception type:. ", StringComparison.Ordinal)); + Verify(innerException.Message.Equals("Assert.ThrowsExceptionAsync failed. Expected exception type:. Actual exception type:. ", StringComparison.Ordinal)); } public void ThrowsExceptionAsyncWithMessageShouldThrowAssertionOnNoException() @@ -103,7 +103,7 @@ public void ThrowsExceptionAsyncWithMessageShouldThrowAssertionOnNoException() Verify(innerException is not null); Assert.AreEqual(typeof(AssertFailedException), innerException.GetType()); - Verify(innerException.Message.Equals("Assert.ThrowsException failed. Expected exception type: but no exception was thrown. The world is not on fire.", StringComparison.Ordinal)); + Verify(innerException.Message.Equals("Assert.ThrowsExceptionAsync failed. Expected exception type: but no exception was thrown. The world is not on fire.", StringComparison.Ordinal)); } public void ThrowsExceptionAsyncWithMessageShouldThrowAssertionOnWrongException() @@ -121,7 +121,7 @@ public void ThrowsExceptionAsyncWithMessageShouldThrowAssertionOnWrongException( Verify(innerException is not null); Assert.AreEqual(typeof(AssertFailedException), innerException.GetType()); - Verify(innerException.Message.Equals("Assert.ThrowsException failed. Expected exception type:. Actual exception type:. Happily ever after.", StringComparison.Ordinal)); + Verify(innerException.Message.Equals("Assert.ThrowsExceptionAsync failed. Expected exception type:. Actual exception type:. Happily ever after.", StringComparison.Ordinal)); } public void ThrowsExceptionAsyncWithMessageAndParamsShouldThrowOnNullAction() @@ -170,7 +170,7 @@ public void ThrowsExceptionAsyncWithMessageAndParamsShouldThrowAssertionOnNoExce Verify(innerException is not null); Assert.AreEqual(typeof(AssertFailedException), innerException.GetType()); - Verify(innerException.Message.Equals("Assert.ThrowsException failed. Expected exception type: but no exception was thrown. The world is not on fire ta.da-123.", StringComparison.Ordinal)); + Verify(innerException.Message.Equals("Assert.ThrowsExceptionAsync failed. Expected exception type: but no exception was thrown. The world is not on fire ta.da-123.", StringComparison.Ordinal)); } public void ThrowsExceptionAsyncWithMessageAndParamsShouldThrowAssertionOnWrongException() @@ -190,7 +190,49 @@ public void ThrowsExceptionAsyncWithMessageAndParamsShouldThrowAssertionOnWrongE Verify(innerException is not null); Assert.AreEqual(typeof(AssertFailedException), innerException.GetType()); - Verify(innerException.Message.Equals("Assert.ThrowsException failed. Expected exception type:. Actual exception type:. Happily ever after. The End.", StringComparison.Ordinal)); + Verify(innerException.Message.Equals("Assert.ThrowsExceptionAsync failed. Expected exception type:. Actual exception type:. Happily ever after. The End.", StringComparison.Ordinal)); } #endregion + + public void Throws_WhenExceptionIsDerivedFromExpectedType_ShouldNotThrow() + => Assert.Throws(() => throw new ArgumentNullException()); + + public void Throws_WhenExceptionIsNotExpectedType_ShouldThrow() + { + static void Action() => Assert.Throws(() => throw new Exception()); + Exception ex = VerifyThrows(Action); + Verify(ex is AssertFailedException); + } + + public void ThrowsExactly_WhenExceptionIsDerivedFromExpectedType_ShouldThrow() + { + static void Action() => Assert.ThrowsExactly(() => throw new ArgumentNullException()); + Exception ex = VerifyThrows(Action); + Verify(ex is AssertFailedException); + } + + public void ThrowsExactly_WhenExceptionExpectedType_ShouldNotThrow() + => Assert.ThrowsExactly(() => throw new ArgumentNullException()); + + public async Task ThrowsAsync_WhenExceptionIsDerivedFromExpectedType_ShouldNotThrow() + => await Assert.ThrowsAsync(() => throw new ArgumentNullException()); + + public void ThrowsAsync_WhenExceptionIsNotExpectedType_ShouldThrow() + { + Task t = Assert.ThrowsAsync(() => throw new Exception()); + Exception ex = VerifyThrows(t.Wait); + Assert.IsInstanceOfType(ex.InnerException, out AssertFailedException assertFailedException); + Assert.AreEqual("Assert.ThrowsAsync failed. Expected exception type:. Actual exception type:. ", assertFailedException.Message); + } + + public void ThrowsExactlyAsync_WhenExceptionIsDerivedFromExpectedType_ShouldThrow() + { + Task t = Assert.ThrowsExactlyAsync(() => throw new ArgumentNullException()); + Exception ex = VerifyThrows(t.Wait); + Assert.IsInstanceOfType(ex.InnerException, out AssertFailedException assertFailedException); + Assert.AreEqual("Assert.ThrowsExactlyAsync failed. Expected exception type:. Actual exception type:. ", assertFailedException.Message); + } + + public async Task ThrowsExactlyAsync_WhenExceptionExpectedType_ShouldNotThrow() + => await Assert.ThrowsExactlyAsync(() => throw new ArgumentNullException()); }