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());
}