// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. using System; using System.ComponentModel; using System.Globalization; using System.Reflection; using System.Web; namespace Microsoft.TestCommon { public partial class AssertEx { /// /// Determines if your thread's current culture and current UI culture is English. /// public static bool CurrentCultureIsEnglish { get { return String.Equals(CultureInfo.CurrentCulture.TwoLetterISOLanguageName, "en", StringComparison.OrdinalIgnoreCase) && String.Equals(CultureInfo.CurrentUICulture.TwoLetterISOLanguageName, "en", StringComparison.OrdinalIgnoreCase); } } /// /// Determines whether the specified exception is of the given type (or optionally of a derived type). /// The exception is not allowed to be null; /// /// The type of the exception to test for. /// The exception to be tested. /// The expected exception message (only verified on US English OSes). /// Pass true to allow exceptions which derive from TException; pass false, otherwise public static void IsException(Type exceptionType, Exception exception, string expectedMessage = null, bool allowDerivedExceptions = false) { exception = UnwrapException(exception); NotNull(exception); if (allowDerivedExceptions) IsAssignableFrom(exceptionType, exception); else IsType(exceptionType, exception); VerifyExceptionMessage(exception, expectedMessage, partialMatch: false); } /// /// Determines whether the specified exception is of the given type (or optionally of a derived type). /// The exception is not allowed to be null; /// /// The type of the exception to test for. /// The exception to be tested. /// The expected exception message (only verified on US English OSes). /// Pass true to allow exceptions which derive from TException; pass false, otherwise /// The exception cast to TException. public static TException IsException(Exception exception, string expectedMessage = null, bool allowDerivedExceptions = false) where TException : Exception { TException result; exception = UnwrapException(exception); NotNull(exception); if (allowDerivedExceptions) result = IsAssignableFrom(exception); else result = IsType(exception); VerifyExceptionMessage(exception, expectedMessage, partialMatch: false); return result; } // We've re-implemented all the xUnit.net Throws code so that we can get this // updated implementation of RecordException which silently unwraps any instances // of AggregateException. This lets our tests better simulate what "await" would do // and thus makes them easier to port to .NET 4.5. private static Exception RecordException(Action testCode) { try { testCode(); return null; } catch (Exception exception) { return UnwrapException(exception); } } /// /// Verifies that the exact exception is thrown (and not a derived exception type). /// /// The type of the exception expected to be thrown /// A delegate to the code to be tested /// The exception that was thrown, when successful /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown public static T Throws(Action testCode) where T : Exception { return (T)Throws(typeof(T), testCode); } /// /// Verifies that the exact exception is thrown (and not a derived exception type). /// Generally used to test property accessors. /// /// The type of the exception expected to be thrown /// A delegate to the code to be tested /// The exception that was thrown, when successful /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown public static T Throws(Func testCode) where T : Exception { return (T)Throws(typeof(T), testCode); } /// /// Verifies that the exact exception is thrown (and not a derived exception type). /// /// The type of the exception expected to be thrown /// A delegate to the code to be tested /// The exception that was thrown, when successful /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown public static Exception Throws(Type exceptionType, Action testCode) { Exception exception = RecordException(testCode); if (exception == null) throw new ThrowsException(exceptionType); if (!exceptionType.Equals(exception.GetType())) throw new ThrowsException(exceptionType, exception); return exception; } /// /// Verifies that the exact exception is thrown (and not a derived exception type). /// Generally used to test property accessors. /// /// The type of the exception expected to be thrown /// A delegate to the code to be tested /// The exception that was thrown, when successful /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown public static Exception Throws(Type exceptionType, Func testCode) { return Throws(exceptionType, () => { object unused = testCode(); }); } /// /// Verifies that an exception of the given type (or optionally a derived type) is thrown. /// /// The type of the exception expected to be thrown /// A delegate to the code to be tested /// Pass true to allow exceptions which derive from TException; pass false, otherwise /// The exception that was thrown, when successful /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown public static TException Throws(Action testCode, bool allowDerivedExceptions) where TException : Exception { Type exceptionType = typeof(TException); Exception exception = RecordException(testCode); TargetInvocationException tie = exception as TargetInvocationException; if (tie != null) { exception = tie.InnerException; } if (exception == null) { throw new ThrowsException(exceptionType); } var typedException = exception as TException; if (typedException == null || (!allowDerivedExceptions && typedException.GetType() != typeof(TException))) { throw new ThrowsException(exceptionType, exception); } return typedException; } /// /// Verifies that an exception of the given type (or optionally a derived type) is thrown. /// Generally used to test property accessors. /// /// The type of the exception expected to be thrown /// A delegate to the code to be tested /// Pass true to allow exceptions which derive from TException; pass false, otherwise /// The exception that was thrown, when successful /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown public static TException Throws(Func testCode, bool allowDerivedExceptions) where TException : Exception { return Throws(() => { testCode(); }, allowDerivedExceptions); } /// /// Verifies that an exception of the given type (or optionally a derived type) is thrown. /// Also verified that the exception message matches if the current thread locale is English. /// /// The type of the exception expected to be thrown /// A delegate to the code to be tested /// The exception message to verify /// Pass true to allow exceptions which derive from TException; pass false, otherwise /// The exception that was thrown, when successful /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown public static TException Throws(Action testCode, string exceptionMessage, bool allowDerivedExceptions = false) where TException : Exception { var ex = Throws(testCode, allowDerivedExceptions); VerifyExceptionMessage(ex, exceptionMessage); return ex; } /// /// Verifies that an exception of the given type (or optionally a derived type) is thrown. /// Also verified that the exception message matches if the current thread locale is English. /// /// The type of the exception expected to be thrown /// A delegate to the code to be tested /// The exception message to verify /// Pass true to allow exceptions which derive from TException; pass false, otherwise /// The exception that was thrown, when successful /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown public static TException Throws(Func testCode, string exceptionMessage, bool allowDerivedExceptions = false) where TException : Exception { return Throws(() => { testCode(); }, exceptionMessage, allowDerivedExceptions); } /// /// Verifies that the code throws an (or optionally any exception which derives from it). /// /// A delegate to the code to be tested /// The name of the parameter that should throw the exception /// Pass true to allow exceptions which derive from TException; pass false, otherwise /// The exception that was thrown, when successful /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown public static ArgumentException ThrowsArgument(Action testCode, string paramName, bool allowDerivedExceptions = false) { var ex = Throws(testCode, allowDerivedExceptions); if (paramName != null) { Equal(paramName, ex.ParamName); } return ex; } /// /// Verifies that the code throws an (or optionally any exception which derives from it). /// /// A delegate to the code to be tested /// The name of the parameter that should throw the exception /// The exception message to verify /// Pass true to allow exceptions which derive from TException; pass false, otherwise /// The exception that was thrown, when successful /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown public static ArgumentException ThrowsArgument(Action testCode, string paramName, string exceptionMessage, bool allowDerivedExceptions = false) { var ex = Throws(testCode, allowDerivedExceptions); if (paramName != null) { Equal(paramName, ex.ParamName); } VerifyExceptionMessage(ex, exceptionMessage, partialMatch: true); return ex; } /// /// Verifies that the code throws an ArgumentException (or optionally any exception which derives from it). /// /// A delegate to the code to be tested /// The name of the parameter that should throw the exception /// Pass true to allow exceptions which derive from TException; pass false, otherwise /// The exception that was thrown, when successful /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown public static ArgumentException ThrowsArgument(Func testCode, string paramName, bool allowDerivedExceptions = false) { var ex = Throws(testCode, allowDerivedExceptions); if (paramName != null) { Equal(paramName, ex.ParamName); } return ex; } /// /// Verifies that the code throws an ArgumentNullException (or optionally any exception which derives from it). /// /// A delegate to the code to be tested /// The name of the parameter that should throw the exception /// The exception that was thrown, when successful /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown public static ArgumentNullException ThrowsArgumentNull(Action testCode, string paramName) { var ex = Throws(testCode, allowDerivedExceptions: false); if (paramName != null) { Equal(paramName, ex.ParamName); } return ex; } /// /// Verifies that the code throws an ArgumentNullException with the expected message that indicates that the value cannot /// be null or empty. /// /// A delegate to the code to be tested /// The name of the parameter that should throw the exception /// The exception that was thrown, when successful /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown public static ArgumentException ThrowsArgumentNullOrEmpty(Action testCode, string paramName) { return Throws(testCode, "Value cannot be null or empty.\r\nParameter name: " + paramName, allowDerivedExceptions: false); } /// /// Verifies that the code throws an ArgumentNullException with the expected message that indicates that the value cannot /// be null or empty string. /// /// A delegate to the code to be tested /// The name of the parameter that should throw the exception /// The exception that was thrown, when successful /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown public static ArgumentException ThrowsArgumentNullOrEmptyString(Action testCode, string paramName) { return ThrowsArgument(testCode, paramName, "Value cannot be null or an empty string.", allowDerivedExceptions: true); } /// /// Verifies that the code throws an ArgumentOutOfRangeException (or optionally any exception which derives from it). /// /// A delegate to the code to be tested /// The name of the parameter that should throw the exception /// The exception message to verify /// Pass true to allow exceptions which derive from TException; pass false, otherwise /// The actual value provided /// The exception that was thrown, when successful /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown public static ArgumentOutOfRangeException ThrowsArgumentOutOfRange(Action testCode, string paramName, string exceptionMessage, bool allowDerivedExceptions = false, object actualValue = null) { exceptionMessage = exceptionMessage != null ? exceptionMessage + "\r\nParameter name: " + paramName + (actualValue != null ? "\r\nActual value was " + actualValue.ToString() + "." : "") : exceptionMessage; var ex = Throws(testCode, exceptionMessage, allowDerivedExceptions); if (paramName != null) { Equal(paramName, ex.ParamName); } return ex; } /// /// Verifies that the code throws an with the expected message that indicates that /// the value must be greater than the given . /// /// A delegate to the code to be tested /// The name of the parameter that should throw the exception /// The actual value provided. /// The expected limit value. /// The exception that was thrown, when successful /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown public static ArgumentOutOfRangeException ThrowsArgumentGreaterThan(Action testCode, string paramName, string value, object actualValue = null) { return ThrowsArgumentOutOfRange(testCode, paramName, String.Format("Value must be greater than {0}.", value), false, actualValue); } /// /// Verifies that the code throws an with the expected message that indicates that /// the value must be greater than or equal to the given value. /// /// A delegate to the code to be tested /// The name of the parameter that should throw the exception /// The expected limit value. /// The exception that was thrown, when successful /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown public static ArgumentOutOfRangeException ThrowsArgumentGreaterThanOrEqualTo(Action testCode, string paramName, string value, object actualValue = null) { return ThrowsArgumentOutOfRange(testCode, paramName, String.Format("Value must be greater than or equal to {0}.", value), false, actualValue); } /// /// Verifies that the code throws an with the expected message that indicates that /// the value must be less than the given . /// /// A delegate to the code to be tested /// The name of the parameter that should throw the exception /// The actual value provided. /// The expected limit value. /// The exception that was thrown, when successful /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown public static ArgumentOutOfRangeException ThrowsArgumentLessThan(Action testCode, string paramName, string maxValue, object actualValue = null) { return ThrowsArgumentOutOfRange(testCode, paramName, String.Format("Value must be less than {0}.", maxValue), false, actualValue); } /// /// Verifies that the code throws an with the expected message that indicates that /// the value must be less than or equal to the given . /// /// A delegate to the code to be tested /// The name of the parameter that should throw the exception /// The actual value provided. /// The expected limit value. /// The exception that was thrown, when successful /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown public static ArgumentOutOfRangeException ThrowsArgumentLessThanOrEqualTo(Action testCode, string paramName, string maxValue, object actualValue = null) { return ThrowsArgumentOutOfRange(testCode, paramName, String.Format("Value must be less than or equal to {0}.", maxValue), false, actualValue); } /// /// Verifies that the code throws an HttpException (or optionally any exception which derives from it). /// /// A delegate to the code to be tested /// The exception message to verify /// The expected HTTP status code of the exception /// Pass true to allow exceptions which derive from TException; pass false, otherwise /// The exception that was thrown, when successful /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown public static HttpException ThrowsHttpException(Action testCode, string exceptionMessage, int httpCode, bool allowDerivedExceptions = false) { var ex = Throws(testCode, exceptionMessage, allowDerivedExceptions); Equal(httpCode, ex.GetHttpCode()); return ex; } /// /// Verifies that the code throws an InvalidEnumArgumentException (or optionally any exception which derives from it). /// /// A delegate to the code to be tested /// The name of the parameter that should throw the exception /// The expected invalid value that should appear in the message /// The type of the enumeration /// Pass true to allow exceptions which derive from TException; pass false, otherwise /// The exception that was thrown, when successful /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown public static InvalidEnumArgumentException ThrowsInvalidEnumArgument(Action testCode, string paramName, int invalidValue, Type enumType, bool allowDerivedExceptions = false) { return Throws( testCode, String.Format("The value of argument '{0}' ({1}) is invalid for Enum type '{2}'.{3}Parameter name: {0}", paramName, invalidValue, enumType.Name, Environment.NewLine), allowDerivedExceptions ); } /// /// Verifies that the code throws an HttpException (or optionally any exception which derives from it). /// /// A delegate to the code to be tested /// The name of the object that was dispose /// Pass true to allow exceptions which derive from TException; pass false, otherwise /// The exception that was thrown, when successful /// Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown public static ObjectDisposedException ThrowsObjectDisposed(Action testCode, string objectName, bool allowDerivedExceptions = false) { var ex = Throws(testCode, allowDerivedExceptions); if (objectName != null) { Equal(objectName, ex.ObjectName); } return ex; } private static Exception UnwrapException(Exception exception) { AggregateException aggEx; while ((aggEx = exception as AggregateException) != null) exception = aggEx.GetBaseException(); return exception; } private static void VerifyExceptionMessage(Exception exception, string expectedMessage, bool partialMatch = false) { if (expectedMessage != null && CurrentCultureIsEnglish) { if (!partialMatch) { Equal(expectedMessage, exception.Message); } else { Contains(expectedMessage, exception.Message); } } } // Custom ThrowsException so we can filter the stack trace. private class ThrowsException : Xunit.Sdk.ThrowsException { public ThrowsException(Type type) : base(type) { } public ThrowsException(Type type, Exception ex) : base(type, ex) { } protected override bool ExcludeStackFrame(string stackFrame) { if (stackFrame.StartsWith("at Microsoft.TestCommon.AssertEx.", StringComparison.OrdinalIgnoreCase)) { return true; } return base.ExcludeStackFrame(stackFrame); } } } }