diff --git a/TUnit.Assertions/Conditions/ExceptionPropertyAssertions.cs b/TUnit.Assertions/Conditions/ExceptionPropertyAssertions.cs index 457859cb14..df9366496e 100644 --- a/TUnit.Assertions/Conditions/ExceptionPropertyAssertions.cs +++ b/TUnit.Assertions/Conditions/ExceptionPropertyAssertions.cs @@ -260,6 +260,107 @@ protected override string GetExpectation() => $"exception message to match {_matcher}"; } +/// +/// Asserts that an exception's StackTrace property contains a specific substring. +/// Chains after a Throws assertion. +/// Example: await Assert.That(() => ThrowingMethod()).Throws<Exception>().WithStackTraceContaining("MyClass.MyMethod"); +/// +public class ExceptionStackTraceContainsAssertion : Assertion + where TException : Exception +{ + private readonly string _expectedSubstring; + private readonly StringComparison _comparison; + + public ExceptionStackTraceContainsAssertion( + AssertionContext context, + string expectedSubstring, + StringComparison comparison = StringComparison.Ordinal) + : base(context) + { + _expectedSubstring = expectedSubstring; + _comparison = comparison; + } + + protected override Task CheckAsync(EvaluationMetadata metadata) + { + var exception = metadata.Value; + var evaluationException = metadata.Exception; + + if (evaluationException != null) + { + return Task.FromResult(AssertionResult.Failed($"threw {evaluationException.GetType().FullName}")); + } + + if (exception == null) + { + return Task.FromResult(AssertionResult.Failed("no exception was thrown")); + } + + if (exception.StackTrace == null) + { + return Task.FromResult(AssertionResult.Failed("exception stack trace was null")); + } + + if (exception.StackTrace.Contains(_expectedSubstring, _comparison)) + { + return AssertionResult._passedTask; + } + + return Task.FromResult(AssertionResult.Failed($"exception stack trace was \"{exception.StackTrace}\"")); + } + + protected override string GetExpectation() => + $"exception stack trace to contain \"{_expectedSubstring}\""; +} + +/// +/// Asserts that an exception has an inner exception of the specified type. +/// Chains after a Throws assertion. +/// Example: await Assert.That(() => ThrowingMethod()).Throws<Exception>().WithInnerException<InvalidOperationException>(); +/// +public class ExceptionInnerExceptionOfTypeAssertion : Assertion + where TException : Exception + where TInnerException : Exception +{ + public ExceptionInnerExceptionOfTypeAssertion( + AssertionContext context) + : base(context) + { + } + + protected override Task CheckAsync(EvaluationMetadata metadata) + { + var exception = metadata.Value; + var evaluationException = metadata.Exception; + + if (evaluationException != null) + { + return Task.FromResult(AssertionResult.Failed($"threw {evaluationException.GetType().FullName}")); + } + + if (exception == null) + { + return Task.FromResult(AssertionResult.Failed("no exception was thrown")); + } + + if (exception.InnerException == null) + { + return Task.FromResult(AssertionResult.Failed("exception has no inner exception")); + } + + if (exception.InnerException is not TInnerException) + { + return Task.FromResult(AssertionResult.Failed( + $"inner exception was {exception.InnerException.GetType().Name} instead of {typeof(TInnerException).Name}")); + } + + return AssertionResult._passedTask; + } + + protected override string GetExpectation() => + $"exception to have inner exception of type {typeof(TInnerException).Name}"; +} + /// /// Asserts that an ArgumentException has a specific parameter name. /// diff --git a/TUnit.Assertions/Conditions/ThrowsAssertion.cs b/TUnit.Assertions/Conditions/ThrowsAssertion.cs index c43f4b85f6..3b4de430b7 100644 --- a/TUnit.Assertions/Conditions/ThrowsAssertion.cs +++ b/TUnit.Assertions/Conditions/ThrowsAssertion.cs @@ -200,6 +200,37 @@ public ExceptionParameterNameAssertion WithParameterName(string expe return new ExceptionParameterNameAssertion(Context, expectedParameterName); } + /// + /// Asserts that the exception has an inner exception of the specified type. + /// Example: await Assert.That(() => ThrowingMethod()).Throws<Exception>().WithInnerException<InvalidOperationException>(); + /// + public ExceptionInnerExceptionOfTypeAssertion WithInnerException() + where TInnerException : Exception + { + Context.ExpressionBuilder.Append($".WithInnerException<{typeof(TInnerException).Name}>()"); + return new ExceptionInnerExceptionOfTypeAssertion(Context); + } + + /// + /// Asserts that the exception's stack trace contains the specified substring. + /// Example: await Assert.That(() => ThrowingMethod()).Throws<Exception>().WithStackTraceContaining("MyClass.MyMethod"); + /// + public ExceptionStackTraceContainsAssertion WithStackTraceContaining(string expectedSubstring) + { + Context.ExpressionBuilder.Append($".WithStackTraceContaining(\"{expectedSubstring}\")"); + return new ExceptionStackTraceContainsAssertion(Context, expectedSubstring); + } + + /// + /// Asserts that the exception's stack trace contains the specified substring using the specified comparison. + /// Example: await Assert.That(() => ThrowingMethod()).Throws<Exception>().WithStackTraceContaining("MyClass", StringComparison.OrdinalIgnoreCase); + /// + public ExceptionStackTraceContainsAssertion WithStackTraceContaining(string expectedSubstring, StringComparison comparison) + { + Context.ExpressionBuilder.Append($".WithStackTraceContaining(\"{expectedSubstring}\", StringComparison.{comparison})"); + return new ExceptionStackTraceContainsAssertion(Context, expectedSubstring, comparison); + } + /// /// Adds runtime Type-based exception checking for non-generic Throws scenarios. /// Returns a specialized assertion that validates against the provided Type. @@ -341,6 +372,37 @@ public ExceptionParameterNameAssertion WithParameterName(string expe Context.ExpressionBuilder.Append($".WithParameterName(\"{expectedParameterName}\")"); return new ExceptionParameterNameAssertion(Context, expectedParameterName, requireExactType: true); } + + /// + /// Asserts that the exception has an inner exception of the specified type. + /// Example: await Assert.That(() => ThrowingMethod()).ThrowsExactly<Exception>().WithInnerException<InvalidOperationException>(); + /// + public ExceptionInnerExceptionOfTypeAssertion WithInnerException() + where TInnerException : Exception + { + Context.ExpressionBuilder.Append($".WithInnerException<{typeof(TInnerException).Name}>()"); + return new ExceptionInnerExceptionOfTypeAssertion(Context); + } + + /// + /// Asserts that the exception's stack trace contains the specified substring. + /// Example: await Assert.That(() => ThrowingMethod()).ThrowsExactly<Exception>().WithStackTraceContaining("MyClass.MyMethod"); + /// + public ExceptionStackTraceContainsAssertion WithStackTraceContaining(string expectedSubstring) + { + Context.ExpressionBuilder.Append($".WithStackTraceContaining(\"{expectedSubstring}\")"); + return new ExceptionStackTraceContainsAssertion(Context, expectedSubstring); + } + + /// + /// Asserts that the exception's stack trace contains the specified substring using the specified comparison. + /// Example: await Assert.That(() => ThrowingMethod()).ThrowsExactly<Exception>().WithStackTraceContaining("MyClass", StringComparison.OrdinalIgnoreCase); + /// + public ExceptionStackTraceContainsAssertion WithStackTraceContaining(string expectedSubstring, StringComparison comparison) + { + Context.ExpressionBuilder.Append($".WithStackTraceContaining(\"{expectedSubstring}\", StringComparison.{comparison})"); + return new ExceptionStackTraceContainsAssertion(Context, expectedSubstring, comparison); + } } /// diff --git a/TUnit.Assertions/Extensions/AssertionExtensions.cs b/TUnit.Assertions/Extensions/AssertionExtensions.cs index d3bada1e50..c2a380f459 100644 --- a/TUnit.Assertions/Extensions/AssertionExtensions.cs +++ b/TUnit.Assertions/Extensions/AssertionExtensions.cs @@ -1057,6 +1057,80 @@ public static ExceptionParameterNameAssertion WithParameterName(source.Context, expectedParameterName); } + /// + /// Asserts that the exception's stack trace contains the specified substring. + /// Works after Throws assertions. + /// Example: await Assert.That(() => ThrowingMethod()).Throws<Exception>().And.WithStackTraceContaining("MyClass.MyMethod"); + /// + public static ExceptionStackTraceContainsAssertion WithStackTraceContaining( + this IAssertionSource source, + string expectedSubstring, + [CallerArgumentExpression(nameof(expectedSubstring))] string? expression = null) + where TException : Exception + { + source.Context.ExpressionBuilder.Append($".WithStackTraceContaining({expression})"); + return new ExceptionStackTraceContainsAssertion(source.Context, expectedSubstring); + } + + /// + /// Asserts that the exception's stack trace contains the specified substring using the specified comparison. + /// Works after Throws assertions. + /// Example: await Assert.That(() => ThrowingMethod()).Throws<Exception>().And.WithStackTraceContaining("MyClass", StringComparison.OrdinalIgnoreCase); + /// + public static ExceptionStackTraceContainsAssertion WithStackTraceContaining( + this IAssertionSource source, + string expectedSubstring, + StringComparison comparison, + [CallerArgumentExpression(nameof(expectedSubstring))] string? expression = null) + where TException : Exception + { + source.Context.ExpressionBuilder.Append($".WithStackTraceContaining({expression}, StringComparison.{comparison})"); + return new ExceptionStackTraceContainsAssertion(source.Context, expectedSubstring, comparison); + } + + /// + /// Asserts that the exception has an inner exception of the specified type. + /// Works after Throws assertions. + /// Example: await Assert.That(() => ThrowingMethod()).Throws<Exception>().And.WithInnerException<InvalidOperationException>(); + /// + public static ExceptionInnerExceptionOfTypeAssertion WithInnerException( + this IAssertionSource source) + where TException : Exception + where TInnerException : Exception + { + source.Context.ExpressionBuilder.Append($".WithInnerException<{typeof(TInnerException).Name}>()"); + return new ExceptionInnerExceptionOfTypeAssertion(source.Context); + } + + /// + /// Alias for WithMessage - asserts that the exception message exactly equals the specified string. + /// Works after Throws assertions. + /// Example: await Assert.That(() => ThrowingMethod()).Throws<Exception>().And.HasMessage("exact message"); + /// + public static ExceptionMessageEqualsAssertion HasMessage( + this IAssertionSource source, + string expectedMessage, + [CallerArgumentExpression(nameof(expectedMessage))] string? expression = null) + where TException : Exception + { + return source.WithMessage(expectedMessage, expression); + } + + /// + /// Alias for WithMessage - asserts that the exception message exactly equals the specified string using the specified comparison. + /// Works after Throws assertions. + /// Example: await Assert.That(() => ThrowingMethod()).Throws<Exception>().And.HasMessage("exact message", StringComparison.OrdinalIgnoreCase); + /// + public static ExceptionMessageEqualsAssertion HasMessage( + this IAssertionSource source, + string expectedMessage, + StringComparison comparison, + [CallerArgumentExpression(nameof(expectedMessage))] string? expression = null) + where TException : Exception + { + return source.WithMessage(expectedMessage, comparison, expression); + } + public static ThrowsAssertion Throws(this DelegateAssertion source) where TException : Exception { var iface = (IAssertionSource)source; diff --git a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet10_0.verified.txt b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet10_0.verified.txt index becf3adaed..fc59f32e84 100644 --- a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet10_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet10_0.verified.txt @@ -1089,6 +1089,14 @@ namespace .Conditions protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } } + public class ExceptionInnerExceptionOfTypeAssertion : . + where TException : + where TInnerException : + { + public ExceptionInnerExceptionOfTypeAssertion(. context) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } public class ExceptionMessageContainsAssertion : . where TException : { @@ -1131,6 +1139,13 @@ namespace .Conditions protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } } + public class ExceptionStackTraceContainsAssertion : . + where TException : + { + public ExceptionStackTraceContainsAssertion(. context, string expectedSubstring, comparison = 4) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } [.("IsNotExecutable")] public class FileIsNotExecutableAssertion : .<.FileInfo> { @@ -1993,6 +2008,8 @@ namespace .Conditions protected override bool CheckExceptionType( actualException, out string? errorMessage) { } public . WithExceptionType( expectedExceptionType) { } public .<> WithInnerException() { } + public . WithInnerException() + where TInnerException : { } public . WithMessage(string expectedMessage) { } public . WithMessage(string expectedMessage, comparison) { } public . WithMessageContaining(string expectedSubstring) { } @@ -2002,6 +2019,8 @@ namespace .Conditions public . WithMessageNotContaining(string notExpectedSubstring) { } public . WithMessageNotContaining(string notExpectedSubstring, comparison) { } public . WithParameterName(string expectedParameterName) { } + public . WithStackTraceContaining(string expectedSubstring) { } + public . WithStackTraceContaining(string expectedSubstring, comparison) { } } public class ThrowsExactlyAssertion : .> where TException : @@ -2009,6 +2028,8 @@ namespace .Conditions public ThrowsExactlyAssertion(. context) { } protected override bool IsExactTypeMatch { get; } protected override bool CheckExceptionType( actualException, out string? errorMessage) { } + public . WithInnerException() + where TInnerException : { } public . WithMessage(string expectedMessage) { } public . WithMessage(string expectedMessage, comparison) { } public . WithMessageContaining(string expectedSubstring) { } @@ -2018,6 +2039,8 @@ namespace .Conditions public . WithMessageNotContaining(string notExpectedSubstring) { } public . WithMessageNotContaining(string notExpectedSubstring, comparison) { } public . WithParameterName(string expectedParameterName) { } + public . WithStackTraceContaining(string expectedSubstring) { } + public . WithStackTraceContaining(string expectedSubstring, comparison) { } } public class ThrowsNothingAssertion : . { @@ -2497,6 +2520,10 @@ namespace .Extensions public static ..LengthWrapper HasLength(this . source) { } [("Use Length().IsEqualTo(expectedLength) instead.")] public static . HasLength(this . source, int expectedLength, [.("expectedLength")] string? expression = null) { } + public static . HasMessage(this . source, string expectedMessage, [.("expectedMessage")] string? expression = null) + where TException : { } + public static . HasMessage(this . source, string expectedMessage, comparison, [.("expectedMessage")] string? expression = null) + where TException : { } public static . HasMessageContaining(this . source, string expectedSubstring, [.("expectedSubstring")] string? expression = null) where TException : { } public static . HasMessageContaining(this . source, string expectedSubstring, comparison, [.("expectedSubstring")] string? expression = null) @@ -2599,6 +2626,9 @@ namespace .Extensions public static . ThrowsNothing(this . source) { } public static . WaitsFor(this . source, <., .> assertionBuilder, timeout, ? pollingInterval = default, [.("timeout")] string? timeoutExpression = null, [.("pollingInterval")] string? pollingIntervalExpression = null) { } public static ..WhenParsedIntoAssertion WhenParsedInto<[.(..None | ..PublicMethods | ..Interfaces)] T>(this . source) { } + public static . WithInnerException(this . source) + where TException : + where TInnerException : { } public static . WithMessage(this . source, string expectedMessage, [.("expectedMessage")] string? expression = null) where TException : { } public static . WithMessage(this . source, string expectedMessage, comparison, [.("expectedMessage")] string? expression = null) @@ -2617,6 +2647,10 @@ namespace .Extensions where TException : { } public static . WithParameterName(this . source, string expectedParameterName, [.("expectedParameterName")] string? expression = null) where TException : { } + public static . WithStackTraceContaining(this . source, string expectedSubstring, [.("expectedSubstring")] string? expression = null) + where TException : { } + public static . WithStackTraceContaining(this . source, string expectedSubstring, comparison, [.("expectedSubstring")] string? expression = null) + where TException : { } } public static class BetweenAssertionExtensions { 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 d3c4bae057..48b633b899 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 @@ -1072,6 +1072,14 @@ namespace .Conditions protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } } + public class ExceptionInnerExceptionOfTypeAssertion : . + where TException : + where TInnerException : + { + public ExceptionInnerExceptionOfTypeAssertion(. context) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } public class ExceptionMessageContainsAssertion : . where TException : { @@ -1114,6 +1122,13 @@ namespace .Conditions protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } } + public class ExceptionStackTraceContainsAssertion : . + where TException : + { + public ExceptionStackTraceContainsAssertion(. context, string expectedSubstring, comparison = 4) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } [.("IsNotExecutable")] public class FileIsNotExecutableAssertion : .<.FileInfo> { @@ -1976,6 +1991,8 @@ namespace .Conditions protected override bool CheckExceptionType( actualException, out string? errorMessage) { } public . WithExceptionType( expectedExceptionType) { } public .<> WithInnerException() { } + public . WithInnerException() + where TInnerException : { } public . WithMessage(string expectedMessage) { } public . WithMessage(string expectedMessage, comparison) { } public . WithMessageContaining(string expectedSubstring) { } @@ -1985,6 +2002,8 @@ namespace .Conditions public . WithMessageNotContaining(string notExpectedSubstring) { } public . WithMessageNotContaining(string notExpectedSubstring, comparison) { } public . WithParameterName(string expectedParameterName) { } + public . WithStackTraceContaining(string expectedSubstring) { } + public . WithStackTraceContaining(string expectedSubstring, comparison) { } } public class ThrowsExactlyAssertion : .> where TException : @@ -1992,6 +2011,8 @@ namespace .Conditions public ThrowsExactlyAssertion(. context) { } protected override bool IsExactTypeMatch { get; } protected override bool CheckExceptionType( actualException, out string? errorMessage) { } + public . WithInnerException() + where TInnerException : { } public . WithMessage(string expectedMessage) { } public . WithMessage(string expectedMessage, comparison) { } public . WithMessageContaining(string expectedSubstring) { } @@ -2001,6 +2022,8 @@ namespace .Conditions public . WithMessageNotContaining(string notExpectedSubstring) { } public . WithMessageNotContaining(string notExpectedSubstring, comparison) { } public . WithParameterName(string expectedParameterName) { } + public . WithStackTraceContaining(string expectedSubstring) { } + public . WithStackTraceContaining(string expectedSubstring, comparison) { } } public class ThrowsNothingAssertion : . { @@ -2476,6 +2499,10 @@ namespace .Extensions public static ..LengthWrapper HasLength(this . source) { } [("Use Length().IsEqualTo(expectedLength) instead.")] public static . HasLength(this . source, int expectedLength, [.("expectedLength")] string? expression = null) { } + public static . HasMessage(this . source, string expectedMessage, [.("expectedMessage")] string? expression = null) + where TException : { } + public static . HasMessage(this . source, string expectedMessage, comparison, [.("expectedMessage")] string? expression = null) + where TException : { } public static . HasMessageContaining(this . source, string expectedSubstring, [.("expectedSubstring")] string? expression = null) where TException : { } public static . HasMessageContaining(this . source, string expectedSubstring, comparison, [.("expectedSubstring")] string? expression = null) @@ -2571,6 +2598,9 @@ namespace .Extensions public static . ThrowsNothing(this . source) { } public static . WaitsFor(this . source, <., .> assertionBuilder, timeout, ? pollingInterval = default, [.("timeout")] string? timeoutExpression = null, [.("pollingInterval")] string? pollingIntervalExpression = null) { } public static ..WhenParsedIntoAssertion WhenParsedInto<[.(..None | ..PublicMethods | ..Interfaces)] T>(this . source) { } + public static . WithInnerException(this . source) + where TException : + where TInnerException : { } public static . WithMessage(this . source, string expectedMessage, [.("expectedMessage")] string? expression = null) where TException : { } public static . WithMessage(this . source, string expectedMessage, comparison, [.("expectedMessage")] string? expression = null) @@ -2589,6 +2619,10 @@ namespace .Extensions where TException : { } public static . WithParameterName(this . source, string expectedParameterName, [.("expectedParameterName")] string? expression = null) where TException : { } + public static . WithStackTraceContaining(this . source, string expectedSubstring, [.("expectedSubstring")] string? expression = null) + where TException : { } + public static . WithStackTraceContaining(this . source, string expectedSubstring, comparison, [.("expectedSubstring")] string? expression = null) + where TException : { } } public static class BetweenAssertionExtensions { 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 a47687f281..ff436895f4 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 @@ -1089,6 +1089,14 @@ namespace .Conditions protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } } + public class ExceptionInnerExceptionOfTypeAssertion : . + where TException : + where TInnerException : + { + public ExceptionInnerExceptionOfTypeAssertion(. context) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } public class ExceptionMessageContainsAssertion : . where TException : { @@ -1131,6 +1139,13 @@ namespace .Conditions protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } } + public class ExceptionStackTraceContainsAssertion : . + where TException : + { + public ExceptionStackTraceContainsAssertion(. context, string expectedSubstring, comparison = 4) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } [.("IsNotExecutable")] public class FileIsNotExecutableAssertion : .<.FileInfo> { @@ -1993,6 +2008,8 @@ namespace .Conditions protected override bool CheckExceptionType( actualException, out string? errorMessage) { } public . WithExceptionType( expectedExceptionType) { } public .<> WithInnerException() { } + public . WithInnerException() + where TInnerException : { } public . WithMessage(string expectedMessage) { } public . WithMessage(string expectedMessage, comparison) { } public . WithMessageContaining(string expectedSubstring) { } @@ -2002,6 +2019,8 @@ namespace .Conditions public . WithMessageNotContaining(string notExpectedSubstring) { } public . WithMessageNotContaining(string notExpectedSubstring, comparison) { } public . WithParameterName(string expectedParameterName) { } + public . WithStackTraceContaining(string expectedSubstring) { } + public . WithStackTraceContaining(string expectedSubstring, comparison) { } } public class ThrowsExactlyAssertion : .> where TException : @@ -2009,6 +2028,8 @@ namespace .Conditions public ThrowsExactlyAssertion(. context) { } protected override bool IsExactTypeMatch { get; } protected override bool CheckExceptionType( actualException, out string? errorMessage) { } + public . WithInnerException() + where TInnerException : { } public . WithMessage(string expectedMessage) { } public . WithMessage(string expectedMessage, comparison) { } public . WithMessageContaining(string expectedSubstring) { } @@ -2018,6 +2039,8 @@ namespace .Conditions public . WithMessageNotContaining(string notExpectedSubstring) { } public . WithMessageNotContaining(string notExpectedSubstring, comparison) { } public . WithParameterName(string expectedParameterName) { } + public . WithStackTraceContaining(string expectedSubstring) { } + public . WithStackTraceContaining(string expectedSubstring, comparison) { } } public class ThrowsNothingAssertion : . { @@ -2497,6 +2520,10 @@ namespace .Extensions public static ..LengthWrapper HasLength(this . source) { } [("Use Length().IsEqualTo(expectedLength) instead.")] public static . HasLength(this . source, int expectedLength, [.("expectedLength")] string? expression = null) { } + public static . HasMessage(this . source, string expectedMessage, [.("expectedMessage")] string? expression = null) + where TException : { } + public static . HasMessage(this . source, string expectedMessage, comparison, [.("expectedMessage")] string? expression = null) + where TException : { } public static . HasMessageContaining(this . source, string expectedSubstring, [.("expectedSubstring")] string? expression = null) where TException : { } public static . HasMessageContaining(this . source, string expectedSubstring, comparison, [.("expectedSubstring")] string? expression = null) @@ -2599,6 +2626,9 @@ namespace .Extensions public static . ThrowsNothing(this . source) { } public static . WaitsFor(this . source, <., .> assertionBuilder, timeout, ? pollingInterval = default, [.("timeout")] string? timeoutExpression = null, [.("pollingInterval")] string? pollingIntervalExpression = null) { } public static ..WhenParsedIntoAssertion WhenParsedInto<[.(..None | ..PublicMethods | ..Interfaces)] T>(this . source) { } + public static . WithInnerException(this . source) + where TException : + where TInnerException : { } public static . WithMessage(this . source, string expectedMessage, [.("expectedMessage")] string? expression = null) where TException : { } public static . WithMessage(this . source, string expectedMessage, comparison, [.("expectedMessage")] string? expression = null) @@ -2617,6 +2647,10 @@ namespace .Extensions where TException : { } public static . WithParameterName(this . source, string expectedParameterName, [.("expectedParameterName")] string? expression = null) where TException : { } + public static . WithStackTraceContaining(this . source, string expectedSubstring, [.("expectedSubstring")] string? expression = null) + where TException : { } + public static . WithStackTraceContaining(this . source, string expectedSubstring, comparison, [.("expectedSubstring")] string? expression = null) + where TException : { } } public static class BetweenAssertionExtensions { diff --git a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.Net4_7.verified.txt b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.Net4_7.verified.txt index a1fe4b0503..f3cc859c01 100644 --- a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.Net4_7.verified.txt +++ b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.Net4_7.verified.txt @@ -921,6 +921,14 @@ namespace .Conditions protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } } + public class ExceptionInnerExceptionOfTypeAssertion : . + where TException : + where TInnerException : + { + public ExceptionInnerExceptionOfTypeAssertion(. context) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } public class ExceptionMessageContainsAssertion : . where TException : { @@ -963,6 +971,13 @@ namespace .Conditions protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } } + public class ExceptionStackTraceContainsAssertion : . + where TException : + { + public ExceptionStackTraceContainsAssertion(. context, string expectedSubstring, comparison = 4) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + } [.("IsNotExecutable")] public class FileIsNotExecutableAssertion : .<.FileInfo> { @@ -1776,6 +1791,8 @@ namespace .Conditions protected override bool CheckExceptionType( actualException, out string? errorMessage) { } public . WithExceptionType( expectedExceptionType) { } public .<> WithInnerException() { } + public . WithInnerException() + where TInnerException : { } public . WithMessage(string expectedMessage) { } public . WithMessage(string expectedMessage, comparison) { } public . WithMessageContaining(string expectedSubstring) { } @@ -1785,6 +1802,8 @@ namespace .Conditions public . WithMessageNotContaining(string notExpectedSubstring) { } public . WithMessageNotContaining(string notExpectedSubstring, comparison) { } public . WithParameterName(string expectedParameterName) { } + public . WithStackTraceContaining(string expectedSubstring) { } + public . WithStackTraceContaining(string expectedSubstring, comparison) { } } public class ThrowsExactlyAssertion : .> where TException : @@ -1792,6 +1811,8 @@ namespace .Conditions public ThrowsExactlyAssertion(. context) { } protected override bool IsExactTypeMatch { get; } protected override bool CheckExceptionType( actualException, out string? errorMessage) { } + public . WithInnerException() + where TInnerException : { } public . WithMessage(string expectedMessage) { } public . WithMessage(string expectedMessage, comparison) { } public . WithMessageContaining(string expectedSubstring) { } @@ -1801,6 +1822,8 @@ namespace .Conditions public . WithMessageNotContaining(string notExpectedSubstring) { } public . WithMessageNotContaining(string notExpectedSubstring, comparison) { } public . WithParameterName(string expectedParameterName) { } + public . WithStackTraceContaining(string expectedSubstring) { } + public . WithStackTraceContaining(string expectedSubstring, comparison) { } } public class ThrowsNothingAssertion : . { @@ -2244,6 +2267,10 @@ namespace .Extensions public static ..LengthWrapper HasLength(this . source) { } [("Use Length().IsEqualTo(expectedLength) instead.")] public static . HasLength(this . source, int expectedLength, [.("expectedLength")] string? expression = null) { } + public static . HasMessage(this . source, string expectedMessage, [.("expectedMessage")] string? expression = null) + where TException : { } + public static . HasMessage(this . source, string expectedMessage, comparison, [.("expectedMessage")] string? expression = null) + where TException : { } public static . HasMessageContaining(this . source, string expectedSubstring, [.("expectedSubstring")] string? expression = null) where TException : { } public static . HasMessageContaining(this . source, string expectedSubstring, comparison, [.("expectedSubstring")] string? expression = null) @@ -2323,6 +2350,9 @@ namespace .Extensions public static . ThrowsNothing(this . source) { } public static . WaitsFor(this . source, <., .> assertionBuilder, timeout, ? pollingInterval = default, [.("timeout")] string? timeoutExpression = null, [.("pollingInterval")] string? pollingIntervalExpression = null) { } public static ..WhenParsedIntoAssertion WhenParsedInto(this . source) { } + public static . WithInnerException(this . source) + where TException : + where TInnerException : { } public static . WithMessage(this . source, string expectedMessage, [.("expectedMessage")] string? expression = null) where TException : { } public static . WithMessage(this . source, string expectedMessage, comparison, [.("expectedMessage")] string? expression = null) @@ -2341,6 +2371,10 @@ namespace .Extensions where TException : { } public static . WithParameterName(this . source, string expectedParameterName, [.("expectedParameterName")] string? expression = null) where TException : { } + public static . WithStackTraceContaining(this . source, string expectedSubstring, [.("expectedSubstring")] string? expression = null) + where TException : { } + public static . WithStackTraceContaining(this . source, string expectedSubstring, comparison, [.("expectedSubstring")] string? expression = null) + where TException : { } } public static class BetweenAssertionExtensions { diff --git a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt index f1106c74fd..8386373c06 100644 --- a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt +++ b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt @@ -511,6 +511,13 @@ namespace public static readonly .DefaultExecutor Instance; protected override . ExecuteAsync(<.> action) { } } + public static class Defaults + { + public static readonly ForcefulExitTimeout; + public static readonly HookTimeout; + public static readonly ProcessExitHookDelay; + public static readonly TestTimeout; + } public abstract class DependencyInjectionDataSourceAttribute : .UntypedDataSourceGeneratorAttribute { protected DependencyInjectionDataSourceAttribute() { } @@ -562,6 +569,7 @@ namespace public void SetDisplayName(string displayName) { } public void SetDisplayNameFormatter( formatterType) { } public void SetPriority(. priority) { } + public void SetRetryBackoff(int backoffMs, double backoffMultiplier) { } public void SetRetryLimit(int retryLimit) { } public void SetRetryLimit(int retryCount, <.TestContext, , int, .> shouldRetry) { } } @@ -1138,7 +1146,10 @@ namespace public class RetryAttribute : .TUnitAttribute, .IScopedAttribute, ., . { public RetryAttribute(int times) { } + public int BackoffMs { get; set; } + public double BackoffMultiplier { get; set; } public int Order { get; } + public []? RetryOnExceptionTypes { get; set; } public ScopeType { get; } public int Times { get; } public . OnTestDiscovered(.DiscoveredTestContext context) { } @@ -1407,6 +1418,8 @@ namespace public [] MethodGenericArguments { get; set; } public required .MethodMetadata MethodMetadata { get; set; } public required string MethodName { get; init; } + public int RetryBackoffMs { get; set; } + public double RetryBackoffMultiplier { get; set; } public int RetryLimit { get; set; } public required ReturnType { get; set; } public required object?[] TestClassArguments { get; set; } @@ -2359,6 +2372,8 @@ namespace .Interfaces } public interface ITestConfiguration { + int RetryBackoffMs { get; } + double RetryBackoffMultiplier { get; } int RetryLimit { get; } ? Timeout { get; } }