// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. using System; using System.ComponentModel; using System.Reflection; using System.Threading.Tasks; using System.Web; namespace Microsoft.TestCommon { public partial class Assert { /// /// 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); return VerifyException(exceptionType, 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, () => { 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 verifies that the exception message matches. /// /// 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. /// /// 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) { if (exceptionMessage != null) { exceptionMessage = exceptionMessage + "\r\nParameter name: " + paramName; if (actualValue != null) { exceptionMessage += String.Format(CultureReplacer.DefaultCulture, "\r\nActual value was {0}.", actualValue); } } 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(CultureReplacer.DefaultCulture, "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(CultureReplacer.DefaultCulture, "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(CultureReplacer.DefaultCulture, "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(CultureReplacer.DefaultCulture, "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) { string message = String.Format(CultureReplacer.DefaultCulture, "The value of argument '{0}' ({1}) is invalid for Enum type '{2}'.{3}Parameter name: {0}", paramName, invalidValue, enumType.Name, Environment.NewLine); return Throws(testCode, message, 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; } /// /// Verifies that an exception of the given type is thrown. /// /// 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 /// /// Unlike other Throws* methods, this method does not enforce running the exception delegate with a known Thread Culture. /// public static async Task ThrowsAsync(Func testCode) where TException : Exception { Exception exception = null; try { // The 'testCode' Task might execute asynchronously in a different thread making it hard to enforce the thread culture. // The correct way to verify exception messages in such a scenario would be to run the task synchronously inside of a // culture enforced block. await testCode(); } catch (Exception ex) { exception = ex; } VerifyException(typeof(TException), exception); return (TException)exception; } // 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. In addition to unwrapping exceptions, this method ensures // that tests are executed in with a known set of Culture and UICulture. This prevents // tests from failing when executed on a non-English machine. private static Exception RecordException(Action testCode) { try { using (new CultureReplacer()) { testCode(); } return null; } catch (Exception exception) { return UnwrapException(exception); } } private static Exception UnwrapException(Exception exception) { AggregateException aggEx = exception as AggregateException; if (aggEx != null) { return aggEx.GetBaseException(); } return exception; } private static Exception VerifyException(Type exceptionType, Exception exception) { if (exception == null) { throw new ThrowsException(exceptionType); } else if (exceptionType != exception.GetType()) { throw new ThrowsException(exceptionType, exception); } return exception; } private static void VerifyExceptionMessage(Exception exception, string expectedMessage, bool partialMatch = false) { if (expectedMessage != null) { if (!partialMatch) { Equal(expectedMessage, exception.Message); } else { Contains(expectedMessage, exception.Message); } } } // Custom ThrowsException so we can filter the stack trace. [Serializable] 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.Assert.", StringComparison.OrdinalIgnoreCase)) { return true; } return base.ExcludeStackFrame(stackFrame); } } } }