diff --git a/TUnit.Assertions.Tests/Assertions/Delegates/Throws.ExactlyTests.cs b/TUnit.Assertions.Tests/Assertions/Delegates/Throws.ExactlyTests.cs new file mode 100644 index 0000000000..b9f4dffcbc --- /dev/null +++ b/TUnit.Assertions.Tests/Assertions/Delegates/Throws.ExactlyTests.cs @@ -0,0 +1,80 @@ +namespace TUnit.Assertions.Tests.Assertions.Delegates; + +public partial class Throws +{ + public class ExactlyTests + { + [Test] + public async Task Fails_For_Code_With_Other_Exceptions() + { + string expectedMessage = """ + Expected action to throw exactly a CustomException, but an OtherException was thrown. + At Assert.That(action).ThrowsExactly() + """; + Exception exception = CreateOtherException(); + Action action = () => throw exception; + + var sut = async () + => await Assert.That(action).ThrowsExactly(); + + await Assert.That(sut).ThrowsException() + .WithMessage(expectedMessage); + } + + [Test] + public async Task Fails_For_Code_With_Subtype_Exceptions() + { + string expectedMessage = """ + Expected action to throw exactly a CustomException, but a SubCustomException was thrown. + At Assert.That(action).ThrowsExactly() + """; + Exception exception = CreateSubCustomException(); + Action action = () => throw exception; + + var sut = async () + => await Assert.That(action).ThrowsExactly(); + + await Assert.That(sut).ThrowsException() + .WithMessage(expectedMessage); + } + + [Test] + public async Task Fails_For_Code_Without_Exceptions() + { + string expectedMessage = """ + Expected action to throw exactly a CustomException, but none was thrown. + At Assert.That(action).ThrowsExactly() + """; + Action action = () => { }; + + var sut = async () + => await Assert.That(action).ThrowsExactly(); + + await Assert.That(sut).ThrowsException() + .WithMessage(expectedMessage); + } + + [Test] + public async Task Returns_Exception_When_Awaited() + { + Exception exception = CreateCustomException(); + Action action = () => throw exception; + + var result = await Assert.That(action).ThrowsExactly(); + + await Assert.That((object?)result).IsSameReference(exception); + } + + [Test] + public async Task Succeeds_For_Code_With_Correct_Exception() + { + Exception exception = CreateCustomException(); + Action action = () => throw exception; + + var sut = async () + => await Assert.That(action).ThrowsExactly(); + + await Assert.That(sut).ThrowsNothing(); + } + } +} diff --git a/TUnit.Assertions.Tests/Assertions/Delegates/Throws.ExceptionTests.cs b/TUnit.Assertions.Tests/Assertions/Delegates/Throws.ExceptionTests.cs new file mode 100644 index 0000000000..e27785b059 --- /dev/null +++ b/TUnit.Assertions.Tests/Assertions/Delegates/Throws.ExceptionTests.cs @@ -0,0 +1,46 @@ +namespace TUnit.Assertions.Tests.Assertions.Delegates; + +public partial class Throws +{ + public class ExceptionTests + { + [Test] + public async Task Fails_For_Code_Without_Exceptions() + { + string expectedMessage = """ + Expected action to throw an exception, but none was thrown. + At Assert.That(action).ThrowsException() + """; + Action action = () => { }; + + var sut = async () + => await Assert.That(action).ThrowsException(); + + await Assert.That(sut).ThrowsException() + .WithMessage(expectedMessage); + } + + [Test] + public async Task Returns_Exception_When_Awaited() + { + Exception exception = CreateCustomException(); + Action action = () => throw exception; + + var result = await Assert.That(action).ThrowsException(); + + await Assert.That((object?)result).IsSameReference(exception); + } + + [Test] + public async Task Succeeds_For_Code_With_Exceptions() + { + Exception exception = CreateCustomException(); + Action action = () => throw exception; + + var sut = async () + => await Assert.That(action).ThrowsException(); + + await Assert.That(sut).ThrowsNothing(); + } + } +} diff --git a/TUnit.Assertions.Tests/Assertions/Delegates/Throws.NothingTests.cs b/TUnit.Assertions.Tests/Assertions/Delegates/Throws.NothingTests.cs new file mode 100644 index 0000000000..ce952e8b87 --- /dev/null +++ b/TUnit.Assertions.Tests/Assertions/Delegates/Throws.NothingTests.cs @@ -0,0 +1,47 @@ +namespace TUnit.Assertions.Tests.Assertions.Delegates; + +public partial class Throws +{ + public class NothingTests + { + [Test] + public async Task Fails_For_Code_With_Exceptions() + { + string expectedMessage = $$""" + Expected action to throw nothing, but a CustomException was thrown: + {{nameof(Fails_For_Code_With_Exceptions)}}. + At Assert.That(action).ThrowsNothing() + """; + Exception exception = CreateCustomException(); + Action action = () => throw exception; + + var sut = async () + => await Assert.That(action).ThrowsNothing(); + + await Assert.That(sut).ThrowsException() + .WithMessage(expectedMessage); + } + + [Test] + public async Task Succeeds_For_Code_Without_Exceptions() + { + Action action = () => { }; + + var sut = async () + => await Assert.That(action).ThrowsNothing(); + + await Assert.That(sut).ThrowsNothing(); + } + + [Test] + public async Task Returns_Awaited_Result() + { + int value = 42; + Func action = () => value; + + var result = await Assert.That(action).ThrowsNothing(); + + await Assert.That(result).IsEqualTo(value); + } + } +} diff --git a/TUnit.Assertions.Tests/Assertions/Delegates/Throws.OfTypeTests.cs b/TUnit.Assertions.Tests/Assertions/Delegates/Throws.OfTypeTests.cs new file mode 100644 index 0000000000..2cf28a9b23 --- /dev/null +++ b/TUnit.Assertions.Tests/Assertions/Delegates/Throws.OfTypeTests.cs @@ -0,0 +1,92 @@ +namespace TUnit.Assertions.Tests.Assertions.Delegates; + +public partial class Throws +{ + public class OfTypeTests + { + [Test] + public async Task Fails_For_Code_With_Other_Exceptions() + { + string expectedMessage = """ + Expected action to throw a CustomException, but an OtherException was thrown. + At Assert.That(action).Throws() + """; + Exception exception = CreateOtherException(); + Action action = () => throw exception; + + var sut = async () + => await Assert.That(action).Throws(); + + await Assert.That(sut).ThrowsException() + .WithMessage(expectedMessage); + } + + [Test] + public async Task Fails_For_Code_With_Supertype_Exceptions() + { + string expectedMessage = """ + Expected action to throw a SubCustomException, but a CustomException was thrown. + At Assert.That(action).Throws() + """; + Exception exception = CreateCustomException(); + Action action = () => throw exception; + + var sut = async () + => await Assert.That(action).Throws(); + + await Assert.That(sut).ThrowsException() + .WithMessage(expectedMessage); + } + + [Test] + public async Task Fails_For_Code_Without_Exceptions() + { + string expectedMessage = """ + Expected action to throw a CustomException, but none was thrown. + At Assert.That(action).Throws() + """; + Action action = () => { }; + + var sut = async () + => await Assert.That(action).Throws(); + + await Assert.That(sut).ThrowsException() + .WithMessage(expectedMessage); + } + + [Test] + public async Task Returns_Exception_When_Awaited() + { + Exception exception = CreateCustomException(); + Action action = () => throw exception; + + var result = await Assert.That(action).Throws(); + + await Assert.That((object?)result).IsSameReference(exception); + } + + [Test] + public async Task Succeeds_For_Code_With_Subtype_Exceptions() + { + Exception exception = CreateSubCustomException(); + Action action = () => throw exception; + + var sut = async () + => await Assert.That(action).Throws(); + + await Assert.That(sut).ThrowsNothing(); + } + + [Test] + public async Task Succeeds_For_Code_With_Exact_Exceptions() + { + Exception exception = CreateCustomException(); + Action action = () => throw exception; + + var sut = async () + => await Assert.That(action).Throws(); + + await Assert.That(sut).ThrowsNothing(); + } + } +} diff --git a/TUnit.Assertions.Tests/Assertions/Delegates/Throws.WithInnerExceptionTests.cs b/TUnit.Assertions.Tests/Assertions/Delegates/Throws.WithInnerExceptionTests.cs new file mode 100644 index 0000000000..fbff03ade3 --- /dev/null +++ b/TUnit.Assertions.Tests/Assertions/Delegates/Throws.WithInnerExceptionTests.cs @@ -0,0 +1,59 @@ +namespace TUnit.Assertions.Tests.Assertions.Delegates; + +public partial class Throws +{ + public class WithInnerExceptionTests + { + [Test] + public async Task Fails_For_Different_Messages_In_Inner_Exception() + { + string outerMessage = "foo"; + string expectedInnerMessage = "bar"; + string expectedMessage = """ + Expected action to throw an Exception which message equals "bar", but it differs at index 0: + ↓ + "some different inner message" + "bar" + ↑. + At Assert.That(action).ThrowsException().WithInnerException().WithMessage(expectedInnerMessage) + """; + Exception exception = CreateCustomException(outerMessage, + CreateCustomException("some different inner message")); + Action action = () => throw exception; + + var sut = async () + => await Assert.That(action).ThrowsException() + .WithInnerException().WithMessage(expectedInnerMessage); + + await Assert.That(sut).ThrowsException() + .WithMessage(expectedMessage); + } + + [Test] + public async Task Returns_Exception_When_Awaited() + { + Exception exception = CreateCustomException( + innerException: CreateCustomException()); + Action action = () => throw exception; + + var result = await Assert.That(action).Throws().WithInnerException(); + + await Assert.That((object?)result).IsSameReference(exception); + } + + [Test] + public async Task Succeed_For_Matching_Message() + { + string outerMessage = "foo"; + string innerMessage = "bar"; + Exception exception = CreateCustomException(outerMessage, CreateCustomException(innerMessage)); + Action action = () => throw exception; + + var sut = async () + => await Assert.That(action).ThrowsException() + .WithInnerException().WithMessage(innerMessage); + + await Assert.That(sut).ThrowsNothing(); + } + } +} diff --git a/TUnit.Assertions.Tests/Assertions/Delegates/Throws.WithMessageMatchingTests.cs b/TUnit.Assertions.Tests/Assertions/Delegates/Throws.WithMessageMatchingTests.cs new file mode 100644 index 0000000000..2363764cb4 --- /dev/null +++ b/TUnit.Assertions.Tests/Assertions/Delegates/Throws.WithMessageMatchingTests.cs @@ -0,0 +1,160 @@ +using TUnit.Assertions.AssertConditions; + +namespace TUnit.Assertions.Tests.Assertions.Delegates; + +public partial class Throws +{ + public class WithMessageMatchingTests + { + [Test] + [Arguments("some message", "*me me*", true)] + [Arguments("some message", "*ME ME*", false)] + [Arguments("some message", "some?message", true)] + [Arguments("some message", "some*message", true)] + [Arguments("some message", "some me?age", false)] + [Arguments("some message", "some me??age", true)] + public async Task Defaults_To_Case_Sensitive_Wildcard_Pattern( + string message, string pattern, bool expectMatch) + { + Exception exception = CreateCustomException(message); + Action action = () => throw exception; + + var sut = async () + => await Assert.That(action).Throws().WithMessageMatching(pattern); + + if (expectMatch) + { + await Assert.That(sut).ThrowsNothing(); + } + else + { + await Assert.That(sut).ThrowsException(); + } + } + + [Test] + public async Task Fails_For_Different_Messages() + { + string message1 = "foo"; + string message2 = "bar"; + string expectedMessage = """ + Expected action to throw a CustomException which message matches "bar", but found "foo". + At Assert.That(action).ThrowsExactly().WithMessageMatching(message2) + """; + Exception exception = CreateCustomException(message1); + Action action = () => throw exception; + + var sut = async () + => await Assert.That(action).ThrowsExactly().WithMessageMatching(message2); + + await Assert.That(sut).ThrowsException() + .WithMessage(expectedMessage); + } + + [Test] + public async Task Returns_Exception_When_Awaited() + { + string matchingMessage = "foo"; + Exception exception = CreateCustomException(matchingMessage); + Action action = () => throw exception; + + var result = await Assert.That(action).Throws().WithMessageMatching(matchingMessage); + + await Assert.That((object?)result).IsSameReference(exception); + } + + [Test] + public async Task Succeeds_For_Matching_Message() + { + string matchingMessage = "foo"; + Exception exception = CreateCustomException(matchingMessage); + Action action = () => throw exception; + + var sut = async () + => await Assert.That(action).Throws().WithMessageMatching(matchingMessage); + + await Assert.That(sut).ThrowsNothing(); + } + + [Test] + [Arguments("some message", "*me me*", true)] + [Arguments("some message", "*ME ME*", true)] + [Arguments("some message", "Some?message", true)] + [Arguments("some message", "some*Message", true)] + [Arguments("some message", "some me?agE", false)] + [Arguments("some message", "some me??agE", true)] + public async Task Supports_Case_Insensitive_Wildcard_Pattern( + string message, string pattern, bool expectMatch) + { + string expectedExpression = "*Assert.That(action).ThrowsException().WithMessageMatching(StringMatcher.AsWildcard(pattern).Ignor*"; + Exception exception = CreateCustomException(message); + Action action = () => throw exception; + + var sut = async () + => await Assert.That(action).ThrowsException() + .WithMessageMatching(StringMatcher.AsWildcard(pattern).IgnoringCase()); + + if (expectMatch) + { + await Assert.That(sut).ThrowsNothing(); + } + else + { + await Assert.That(sut).ThrowsException().WithMessageMatching(expectedExpression); + } + } + + [Test] + [Arguments("A 1st message", ".*", true)] + [Arguments("A 1st message", "A \\d.*", true)] + [Arguments("A 1st message", "a \\d.*", false)] + [Arguments("A 1st message", "\\dst", true)] + [Arguments("A 1st message", "^\\dst", false)] + [Arguments("A 1st message", "s{2,}", true)] + [Arguments("A 1st message", "s{3,}", false)] + public async Task Supports_Regex_Pattern( + string message, string pattern, bool expectMatch) + { + string expectedExpression = "*Assert.That(action).ThrowsException().WithMessageMatching(StringMatcher.AsRegex(pattern))*"; + Exception exception = CreateCustomException(message); + Action action = () => throw exception; + + var sut = async () + => await Assert.That(action).ThrowsException() + .WithMessageMatching(StringMatcher.AsRegex(pattern)); + + if (expectMatch) + { + await Assert.That(sut).ThrowsNothing(); + } + else + { + await Assert.That(sut).ThrowsException().WithMessageMatching(expectedExpression); + } + } + + [Test] + [Arguments("A 1st message", "a \\d.*", true)] + [Arguments("A 1st message", "^\\dst", false)] + public async Task Supports_Case_Insensitive_Regex_Pattern( + string message, string pattern, bool expectMatch) + { + string expectedExpression = "*Assert.That(action).ThrowsException().WithMessageMatching(StringMatcher.AsRegex(pattern).Ignoring*"; + Exception exception = CreateCustomException(message); + Action action = () => throw exception; + + var sut = async () + => await Assert.That(action).ThrowsException() + .WithMessageMatching(StringMatcher.AsRegex(pattern).IgnoringCase()); + + if (expectMatch) + { + await Assert.That(sut).ThrowsNothing(); + } + else + { + await Assert.That(sut).ThrowsException().WithMessageMatching(expectedExpression); + } + } + } +} diff --git a/TUnit.Assertions.Tests/Assertions/Delegates/Throws.WithMessageTests.cs b/TUnit.Assertions.Tests/Assertions/Delegates/Throws.WithMessageTests.cs new file mode 100644 index 0000000000..27a60e9b22 --- /dev/null +++ b/TUnit.Assertions.Tests/Assertions/Delegates/Throws.WithMessageTests.cs @@ -0,0 +1,55 @@ +namespace TUnit.Assertions.Tests.Assertions.Delegates; + +public partial class Throws +{ + public class WithMessageTests + { + [Test] + public async Task Fails_For_Different_Messages() + { + string message1 = "foo"; + string message2 = "bar"; + string expectedMessage = """ + Expected action to throw a CustomException which message equals "bar", but it differs at index 0: + ↓ + "foo" + "bar" + ↑. + At Assert.That(action).ThrowsExactly().WithMessage(message2) + """; + Exception exception = CreateCustomException(message1); + Action action = () => throw exception; + + var sut = async () + => await Assert.That(action).ThrowsExactly().WithMessage(message2); + + await Assert.That(sut).ThrowsException() + .WithMessage(expectedMessage); + } + + [Test] + public async Task Returns_Exception_When_Awaited() + { + string matchingMessage = "foo"; + Exception exception = CreateCustomException(matchingMessage); + Action action = () => throw exception; + + var result = await Assert.That(action).Throws().WithMessage(matchingMessage); + + await Assert.That((object?)result).IsSameReference(exception); + } + + [Test] + public async Task Succeed_For_Matching_Message() + { + string matchingMessage = "foo"; + Exception exception = CreateCustomException(matchingMessage); + Action action = () => throw exception; + + var sut = async () + => await Assert.That(action).Throws().WithMessage(matchingMessage); + + await Assert.That(sut).ThrowsNothing(); + } + } +} diff --git a/TUnit.Assertions.Tests/Assertions/Delegates/Throws.cs b/TUnit.Assertions.Tests/Assertions/Delegates/Throws.cs new file mode 100644 index 0000000000..4b7015f316 --- /dev/null +++ b/TUnit.Assertions.Tests/Assertions/Delegates/Throws.cs @@ -0,0 +1,54 @@ +using System.Runtime.CompilerServices; + +namespace TUnit.Assertions.Tests.Assertions.Delegates; + +public partial class Throws +{ + private static CustomException CreateCustomException( + [CallerMemberName] string message = "", + Exception? innerException = null) + { + return new CustomException(message, innerException); + } + + private static SubCustomException CreateSubCustomException( + [CallerMemberName] string message = "", + Exception? innerException = null) + { + return new SubCustomException(message, innerException); + } + + private static OtherException CreateOtherException( + [CallerMemberName] string message = "", + Exception? innerException = null) + { + return new OtherException(message, innerException); + } + + private class CustomException : System.Exception + { + public CustomException(string message, Exception? innerException = null) + : base(message, innerException) + { + + } + } + + private class SubCustomException : CustomException + { + public SubCustomException(string message, Exception? innerException = null) + : base(message, innerException) + { + + } + } + + private class OtherException : System.Exception + { + public OtherException(string message, Exception? innerException = null) + : base(message, innerException) + { + + } + } +} diff --git a/TUnit.Assertions.Tests/SatisfiesTests.cs b/TUnit.Assertions.Tests/SatisfiesTests.cs index 50b4fc5b87..efd4fe98cf 100644 --- a/TUnit.Assertions.Tests/SatisfiesTests.cs +++ b/TUnit.Assertions.Tests/SatisfiesTests.cs @@ -89,11 +89,8 @@ public async Task Satisfies_On_Direct_Property_Throws() await Assert.That(async () => await Assert.That(myModel) .Satisfies(model => model.Value, assert => assert.IsEqualTo("Blah")!) - ).ThrowsException() - .OfType() - .And - .ThrowsException() - .With.Message.Containing("Expected model => model.Value to satisfy assert => assert.IsEqualTo(\"Blah\")!, but found \"Hello\" which differs at index 0:"); + ).Throws() + .WithMessageMatching("*Expected model => model.Value to satisfy assert => assert.IsEqualTo(\"Blah\")!, but found \"Hello\" which differs at index 0:*"); } [Test] @@ -121,13 +118,10 @@ await Assert.That(myModel) ) ) ) - ).ThrowsException() - .OfType() - .And - .ThrowsException() - .With.Message.Containing( + ).Throws() + .WithMessageMatching( """ - Expected model => model.Nested to satisfy assert => + *Expected model => model.Nested to satisfy assert => assert.Satisfies(model => model?.Nested, innerAssert => innerAssert.Satisfies(model => model?.Value, innerAssert2 => innerAssert2.IsEqualTo("Blah")! @@ -136,7 +130,7 @@ await Assert.That(myModel) ↓ "Baz" "Blah" - ↑. + ↑.* """ ); } @@ -152,11 +146,8 @@ public async Task Async_Satisfies_On_Direct_Property_Throws() await Assert.That(async () => await Assert.That(myModel) .Satisfies(model => model.Value, assert => assert.IsEqualTo("Blah")!) - ).ThrowsException() - .OfType() - .And - .ThrowsException() - .With.Message.Containing("Expected model => model.Value to satisfy assert => assert.IsEqualTo(\"Blah\")!, but found \"Hello\" which differs at index 0:"); + ).Throws() + .WithMessageMatching("*Expected model => model.Value to satisfy assert => assert.IsEqualTo(\"Blah\")!, but found \"Hello\" which differs at index 0:*"); } [Test] @@ -184,13 +175,10 @@ await Assert.That(myModel) ) ) ) - ).ThrowsException() - .OfType() - .And - .ThrowsException() - .With.Message.Containing( + ).Throws() + .WithMessageMatching( """ - Expected model => model.Nested to satisfy assert => + *Expected model => model.Nested to satisfy assert => assert.Satisfies(model => model?.Nested, innerAssert => innerAssert.Satisfies(model => model?.Value, innerAssert2 => innerAssert2.IsEqualTo("Baz")! @@ -201,7 +189,7 @@ await Assert.That(myModel) "Baz" ↑. At Assert.That(myModel).Satisfies(model => model.Nested, assert => - assert.Sati... + assert.Sati...* """ ); } diff --git a/TUnit.Assertions.Tests/ThrowTests.cs b/TUnit.Assertions.Tests/ThrowTests.cs deleted file mode 100644 index b6ae8359cb..0000000000 --- a/TUnit.Assertions.Tests/ThrowTests.cs +++ /dev/null @@ -1,313 +0,0 @@ -using System.Runtime.CompilerServices; - -namespace TUnit.Assertions.Tests; - -public class ThrowTests -{ - public class ThrowsNothing - { - [Test] - public async Task Fails_For_Code_With_Exceptions() - { - var expectedMessage = """ - Expected action to throw nothing, but a CustomException was thrown. - At Assert.That(action).ThrowsNothing() - """; - Exception exception = CustomException.Create(); - Action action = () => throw exception; - - var sut = async () - => await Assert.That(action).ThrowsNothing(); - - await Assert.That(sut).ThrowsException() - .With.Message.EqualTo(expectedMessage); - } - - [Test] - public async Task Succeeds_For_Code_Without_Exceptions() - { - var action = () => { }; - - var sut = async () - => await Assert.That(action).ThrowsNothing(); - - await Assert.That(sut).ThrowsNothing(); - } - } - - public class ThrowsException - { - [Test] - public async Task Fails_For_Code_Without_Exceptions() - { - var expectedMessage = """ - Expected action to throw an exception, but none was thrown. - At Assert.That(action).ThrowsException.OfAnyType() - """; - var action = () => { }; - - var sut = async () - => await Assert.That(action).ThrowsException(); - - await Assert.That(sut).ThrowsException() - .With.Message.EqualTo(expectedMessage); - } - - [Test] - public async Task Succeeds_For_Code_With_Exceptions() - { - Exception exception = CustomException.Create(); - Action action = () => throw exception; - - var sut = async () - => await Assert.That(action).ThrowsException(); - - await Assert.That(sut).ThrowsNothing(); - } - } - - public class ThrowsException_OfType - { - [Test] - public async Task Fails_For_Code_With_Other_Exceptions() - { - var expectedMessage = """ - Expected action to throw exactly a CustomException, but an OtherException was thrown. - At Assert.That(action).ThrowsException.OfType(CustomException) - """; - Exception exception = OtherException.Create(); - Action action = () => throw exception; - - var sut = async () - => await Assert.That(action).ThrowsException().OfType(); - - await Assert.That(sut).ThrowsException() - .With.Message.EqualTo(expectedMessage); - } - - [Test] - public async Task Fails_For_Code_With_Subtype_Exceptions() - { - var expectedMessage = """ - Expected action to throw exactly a CustomException, but a SubCustomException was thrown. - At Assert.That(action).ThrowsException.OfType(CustomException) - """; - Exception exception = SubCustomException.Create(); - Action action = () => throw exception; - - var sut = async () - => await Assert.That(action).ThrowsException().OfType(); - - await Assert.That(sut).ThrowsException() - .With.Message.EqualTo(expectedMessage); - } - - [Test] - public async Task Fails_For_Code_Without_Exceptions() - { - var expectedMessage = """ - Expected action to throw exactly a CustomException, but none was thrown. - At Assert.That(action).ThrowsException.OfType(CustomException) - """; - var action = () => { }; - - var sut = async () - => await Assert.That(action).ThrowsException().OfType(); - - await Assert.That(sut).ThrowsException() - .With.Message.EqualTo(expectedMessage); - } - - [Test] - public async Task Succeeds_For_Code_With_Correct_Exception() - { - Exception exception = CustomException.Create(); - Action action = () => throw exception; - - var sut = async () - => await Assert.That(action).ThrowsException().OfType(); - - await Assert.That(sut).ThrowsNothing(); - } - } - - public class ThrowsException_SubClassOf - { - [Test] - public async Task Fails_For_Code_With_Other_Exceptions() - { - var expectedMessage = """ - Expected action to throw a CustomException, but an OtherException was thrown. - At Assert.That(action).ThrowsException.SubClassOf(CustomException) - """; - Exception exception = OtherException.Create(); - Action action = () => throw exception; - - var sut = async () - => await Assert.That(action).ThrowsException().SubClassOf(); - - await Assert.That(sut).ThrowsException() - .With.Message.EqualTo(expectedMessage); - } - - [Test] - public async Task Fails_For_Code_Without_Exceptions() - { - var expectedMessage = """ - Expected action to throw a CustomException, but none was thrown. - At Assert.That(action).ThrowsException.SubClassOf(CustomException) - """; - var action = () => { }; - - var sut = async () - => await Assert.That(action).ThrowsException().SubClassOf(); - - await Assert.That(sut).ThrowsException() - .With.Message.EqualTo(expectedMessage); - } - - [Test] - public async Task Succeeds_For_Code_With_Subtype_Exceptions() - { - Exception exception = SubCustomException.Create(); - Action action = () => throw exception; - - var sut = async () - => await Assert.That(action).ThrowsException().SubClassOf(); - - await Assert.That(sut).ThrowsNothing(); - } - } - - public class ThrowsException_With_Message_EqualTo - { - [Test] - public async Task Fails_For_Exceptions_With_Different_Message() - { - var expectedMessage = """ - Expected action to have Message equal to "Fails_For_Some_Other_Reason", but it differs at index 10: - ↓ - "Fails_For_Exceptions_With_Different_Message" - "Fails_For_Some_Other_Reason" - ↑. - At Assert.That(action).ThrowsException.With.Message.EqualTo("Fails_For_Some_Other_Reason", StringCompar... - """; - - Exception exception = CustomException.Create(); - Action action = () => throw exception; - - var sut = async () - => await Assert.That(action).ThrowsException() - .With.Message.EqualTo("Fails_For_Some_Other_Reason"); - - await Assert.That(sut).ThrowsException() - .With.Message.EqualTo(expectedMessage); - } - - [Test] - public async Task Fails_For_Exceptions_With_Different_Multiline_Message() - { - var newline = $"{Environment.NewLine}".Replace("\n", "\\n").Replace("\r", "\\r"); - var longCommonString = """ - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, - sed diam nonumy eirmod tempor invidunt ut labore et dolore - magna aliquyam erat, sed diam voluptua. At vero eos et - accusam et justo duo dolores et ea rebum. Stet clita kasd - gubergren, no sea takimata sanctus est Lorem ipsum dolor - sit amet. - """; - var expectedMessage = $$""" - Expected action to have Message equal to "Lorem ipsum dolor sit amet, consetetur sadipscing elitr,{{newline}}sed diam nonumy eirmod tempor invidunt u…", but it differs at index 302: - ↓ - "ipsum dolor\r\nsit amet.\r\nsome value" - "ipsum dolor\r\nsit amet.\r\nanother value" - ↑. - At Assert.That(action).ThrowsException.With.Message.EqualTo($"{longCommonString}{Environment.NewLine}an... - """; - -#pragma warning disable TUnitAssertions0003 - Exception exception = CustomException.Create($"{longCommonString}{Environment.NewLine}some value"); -#pragma warning restore TUnitAssertions0003 - Action action = () => throw exception; - - var sut = async () - => await Assert.That(action).ThrowsException() - .With.Message.EqualTo($"{longCommonString}{Environment.NewLine}another value"); - - await Assert.That(sut).ThrowsException() - .With.Message.EqualTo(expectedMessage); - } - - [Test] - public async Task Succeeds_For_Exceptions_With_Equal_Message() - { - var expectedMessage = nameof(Succeeds_For_Exceptions_With_Equal_Message); - Exception exception = CustomException.Create(); - Action action = () => throw exception; - - var sut = async () - => await Assert.That(action).ThrowsException().With.Message.EqualTo(expectedMessage); - - await Assert.That(sut).ThrowsNothing(); - } - } - - public class ThrowsException_With_Message_Containing - { - [Test] - public async Task Fails_For_Exceptions_With_Different_Message() - { - var expectedMessage = """ - Expected action to have Message containing "Fails_For_Some_Other_Reason", but it was not found. - At Assert.That(action).ThrowsException.With.Message.Containing("Fails_For_Some_Other_Reason", StringCom... - """; - Exception exception = CustomException.Create(); - Action action = () => throw exception; - - var sut = async () - => await Assert.That(action).ThrowsException() - .With.Message.Containing("Fails_For_Some_Other_Reason"); - - await Assert.That(sut).ThrowsException() - .With.Message.EqualTo(expectedMessage); - } - - [Test] - public async Task Succeeds_For_Exceptions_With_Equal_Message() - { - Exception exception = CustomException.Create(); - Action action = () => throw exception; - - var sut = async () - => await Assert.That(action).ThrowsException() - .With.Message.Containing(nameof(Succeeds_For_Exceptions_With_Equal_Message)); - - await Assert.That(sut).ThrowsNothing(); - } - } - - private class CustomException(string message) : Exception(message) - { - public static CustomException Create([CallerMemberName] string message = "") - { - return new CustomException(message); - } - } - - private class SubCustomException(string message) : CustomException(message) - { - public new static SubCustomException Create([CallerMemberName] string message = "") - { - return new SubCustomException(message); - } - } - - private class OtherException(string message) : Exception(message) - { - public static OtherException Create([CallerMemberName] string message = "") - { - return new OtherException(message); - } - } - -} diff --git a/TUnit.Assertions.UnitTests/DefaultAssertionTests.cs b/TUnit.Assertions.UnitTests/DefaultAssertionTests.cs index d6341efd5a..6237e29d5c 100644 --- a/TUnit.Assertions.UnitTests/DefaultAssertionTests.cs +++ b/TUnit.Assertions.UnitTests/DefaultAssertionTests.cs @@ -18,7 +18,7 @@ await TUnitAssert.That(async () => { string? s = "1"; await TUnitAssert.That(s).IsDefault(); - }).ThrowsException().OfType(); + }).Throws(); } [Test] @@ -35,7 +35,7 @@ await TUnitAssert.That(async () => { int x = 1; await TUnitAssert.That(x).IsDefault(); - }).ThrowsException().OfType(); + }).Throws(); } [Test] @@ -52,7 +52,7 @@ await TUnitAssert.That(async () => { var dt = DateTime.Now; await TUnitAssert.That(dt).IsDefault(); - }).ThrowsException().OfType(); + }).Throws(); } [Test] @@ -62,7 +62,7 @@ await TUnitAssert.That(async () => { string? s = null; await TUnitAssert.That(s).IsNotDefault(); - }).ThrowsException().OfType(); + }).Throws(); } [Test] @@ -79,7 +79,7 @@ await TUnitAssert.That(async () => { int x = 0; await TUnitAssert.That(x).IsNotDefault(); - }).ThrowsException().OfType(); + }).Throws(); } [Test] @@ -96,7 +96,7 @@ await TUnitAssert.That(async () => { DateTime dt = default; await TUnitAssert.That(dt).IsNotDefault(); - }).ThrowsException().OfType(); + }).Throws(); } [Test] diff --git a/TUnit.Assertions.UnitTests/ExceptionAssertionTests.cs b/TUnit.Assertions.UnitTests/ExceptionAssertionTests.cs index 2ad5e93e66..bca912de78 100644 --- a/TUnit.Assertions.UnitTests/ExceptionAssertionTests.cs +++ b/TUnit.Assertions.UnitTests/ExceptionAssertionTests.cs @@ -9,11 +9,11 @@ public async Task Assertion_Message_Has_Correct_doNotPopulateThisValue2() { await TUnitAssert.That(InnerExceptionThrower.Throw) .ThrowsException() - .With.InnerException - .With.InnerException - .With.InnerException - .With.InnerException - .With.InnerException - .With.Message.EqualTo("Message 6"); + .WithInnerException() + .WithInnerException() + .WithInnerException() + .WithInnerException() + .WithInnerException() + .WithMessage("Message 6"); } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/DelegateAssertCondition.cs b/TUnit.Assertions/AssertConditions/DelegateAssertCondition.cs index 15e82be014..8f684777de 100644 --- a/TUnit.Assertions/AssertConditions/DelegateAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/DelegateAssertCondition.cs @@ -38,7 +38,7 @@ protected override Task GetResult(TActual? actualValue, Excepti return AssertionResult.Passed; } - protected virtual string GetFailureMessage(TException? exception) => "TODO VAB"; + protected virtual string GetFailureMessage(TException? exception) => ""; protected override string GetExpectation() { diff --git a/TUnit.Assertions/AssertConditions/Interfaces/IDelegateSource.cs b/TUnit.Assertions/AssertConditions/Interfaces/IDelegateSource.cs index 5dd17c08c8..921d0dad59 100644 --- a/TUnit.Assertions/AssertConditions/Interfaces/IDelegateSource.cs +++ b/TUnit.Assertions/AssertConditions/Interfaces/IDelegateSource.cs @@ -1,3 +1,13 @@ -namespace TUnit.Assertions.AssertConditions.Interfaces; +using TUnit.Assertions.AssertConditions.Throws; +using TUnit.Assertions.AssertionBuilders; -public interface IDelegateSource : ISource; \ No newline at end of file +namespace TUnit.Assertions.AssertConditions.Interfaces; + +public interface IDelegateSource : ISource +{ + public CastableAssertionBuilder ThrowsNothing(); + public ThrowsException ThrowsException(); + public ThrowsException Throws() where TException : Exception; + public ThrowsException ThrowsExactly() where TException : Exception; + +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Operators/DelegateAnd.cs b/TUnit.Assertions/AssertConditions/Operators/DelegateAnd.cs index 743dcd6796..84785e4008 100644 --- a/TUnit.Assertions/AssertConditions/Operators/DelegateAnd.cs +++ b/TUnit.Assertions/AssertConditions/Operators/DelegateAnd.cs @@ -1,4 +1,5 @@ using TUnit.Assertions.AssertConditions.Interfaces; +using TUnit.Assertions.AssertConditions.Throws; using TUnit.Assertions.AssertionBuilders; namespace TUnit.Assertions.AssertConditions.Operators; @@ -9,6 +10,26 @@ public static DelegateAnd Create(AssertionBuilder assertionBui { return new DelegateAnd(assertionBuilder); } - + + public ThrowsException Throws() where TException : Exception + { + return new DelegateSource(assertionBuilder).Throws(); + } + + public ThrowsException ThrowsExactly() where TException : Exception + { + return new DelegateSource(assertionBuilder).ThrowsExactly(); + } + + public ThrowsException ThrowsException() + { + return new DelegateSource(assertionBuilder).ThrowsException(); + } + + public CastableAssertionBuilder ThrowsNothing() + { + return new DelegateSource(assertionBuilder).ThrowsNothing(); + } + AssertionBuilder ISource.AssertionBuilder => new AndAssertionBuilder(assertionBuilder); } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Operators/DelegateOr.cs b/TUnit.Assertions/AssertConditions/Operators/DelegateOr.cs index 07f5235f30..e09f54c833 100644 --- a/TUnit.Assertions/AssertConditions/Operators/DelegateOr.cs +++ b/TUnit.Assertions/AssertConditions/Operators/DelegateOr.cs @@ -1,4 +1,5 @@ using TUnit.Assertions.AssertConditions.Interfaces; +using TUnit.Assertions.AssertConditions.Throws; using TUnit.Assertions.AssertionBuilders; namespace TUnit.Assertions.AssertConditions.Operators; @@ -10,5 +11,25 @@ public static DelegateOr Create(AssertionBuilder assertionBuil return new DelegateOr(assertionBuilder); } + public ThrowsException Throws() where TException : Exception + { + return new DelegateSource(assertionBuilder).Throws(); + } + + public ThrowsException ThrowsExactly() where TException : Exception + { + return new DelegateSource(assertionBuilder).ThrowsExactly(); + } + + public ThrowsException ThrowsException() + { + return new DelegateSource(assertionBuilder).ThrowsException(); + } + + public CastableAssertionBuilder ThrowsNothing() + { + return new DelegateSource(assertionBuilder).ThrowsNothing(); + } + AssertionBuilder ISource.AssertionBuilder => new OrAssertionBuilder(assertionBuilder); } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Operators/DelegateSource.cs b/TUnit.Assertions/AssertConditions/Operators/DelegateSource.cs new file mode 100644 index 0000000000..72e0f93d5b --- /dev/null +++ b/TUnit.Assertions/AssertConditions/Operators/DelegateSource.cs @@ -0,0 +1,44 @@ +using TUnit.Assertions.AssertConditions.Interfaces; +using TUnit.Assertions.AssertConditions.Throws; +using TUnit.Assertions.AssertionBuilders; +using TUnit.Assertions.Extensions; + +namespace TUnit.Assertions.AssertConditions.Operators; + +public class DelegateSource(AssertionBuilder assertionBuilder) : IDelegateSource +{ + AssertionBuilder ISource.AssertionBuilder { get; } = assertionBuilder; + + public ThrowsException Throws() + where TException : Exception + { + return new ThrowsException( + this.RegisterAssertion(new ThrowsOfTypeAssertCondition(), [], $"{nameof(Throws)}<{typeof(TException).Name}>"), + this, + e => e); + } + + public ThrowsException ThrowsExactly() + where TException : Exception + { + return new ThrowsException( + this.RegisterAssertion(new ThrowsExactTypeOfDelegateAssertCondition(), [], $"{nameof(ThrowsExactly)}<{typeof(TException).Name}>"), + this, + e => e); + } + + public ThrowsException ThrowsException() + { + return new ThrowsException( + this.RegisterAssertion(new ThrowsAnyExceptionAssertCondition(), []), + this, + e => e); + } + + public CastableAssertionBuilder ThrowsNothing() + { + return new CastableAssertionBuilder( + this.RegisterAssertion(new ThrowsNothingAssertCondition(), []), + assertionData => assertionData.Result); + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/StringMatcher.cs b/TUnit.Assertions/AssertConditions/StringMatcher.cs new file mode 100644 index 0000000000..9ab3a03b93 --- /dev/null +++ b/TUnit.Assertions/AssertConditions/StringMatcher.cs @@ -0,0 +1,114 @@ +using System.Text.RegularExpressions; + +namespace TUnit.Assertions.AssertConditions; + +/// +/// Matches a against a pattern. +/// +public abstract class StringMatcher +{ + /// + /// Matches the against the given match pattern. + /// + /// The value to match against the given pattern. + internal abstract bool Matches(string? value); + + /// + /// Implicitly converts the to a pattern.
+ /// Supports * to match zero or more characters and ? to match exactly one character. + ///
+ public static implicit operator StringMatcher(string pattern) => AsWildcard(pattern); + + /// + /// A wildcard match.
+ /// Supports * to match zero or more characters and ? to match exactly one character. + ///
+ public static WildcardMatch AsWildcard(string pattern) + => new WildcardMatch(pattern); + + /// + /// A match. + /// + public static RegexMatch AsRegex(string pattern) + => new RegexMatch(pattern); + + public sealed class WildcardMatch : StringMatcher + { + private readonly string _originalPattern; + private readonly RegexMatch _regexMatch; + + internal WildcardMatch(string pattern) + { + _originalPattern = pattern; + _regexMatch = new RegexMatch(WildcardToRegularExpression(pattern)); + } + + /// + internal override bool Matches(string? value) + { + return _regexMatch.Matches(value); + } + + /// + /// Specifies if the match should be performed case-sensitive or not. + /// + public WildcardMatch IgnoringCase(bool ignoreCase = true) + { + _regexMatch.IgnoringCase(ignoreCase); + return this; + } + + /// + public override string ToString() + => _originalPattern; + + private static string WildcardToRegularExpression(string value) + { + string regex = Regex.Escape(value) + .Replace("\\?", ".") + .Replace("\\*", ".*"); + return $"^{regex}$"; + } + } + + public sealed class RegexMatch : StringMatcher + { + private readonly string _pattern; + private bool _ignoreCase; + + internal RegexMatch(string pattern) + { + _pattern = pattern; + } + + /// + internal override bool Matches(string? value) + { + if (value == null) + { + return false; + } + + RegexOptions options = RegexOptions.Multiline; + if (_ignoreCase) + { + options |= RegexOptions.IgnoreCase; + } + + return Regex.IsMatch(value, _pattern, options); + } + + /// + /// Specifies if the match should be performed case-sensitive or not. + /// + public RegexMatch IgnoringCase(bool ignoreCase = true) + { + _ignoreCase = ignoreCase; + return this; + } + + /// + public override string ToString() + => _pattern; + } +} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Throws/ExceptionWith.cs b/TUnit.Assertions/AssertConditions/Throws/ExceptionWith.cs deleted file mode 100644 index 5cbac4c37b..0000000000 --- a/TUnit.Assertions/AssertConditions/Throws/ExceptionWith.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Runtime.CompilerServices; -using TUnit.Assertions.AssertConditions.Interfaces; -using TUnit.Assertions.AssertionBuilders; - -namespace TUnit.Assertions.AssertConditions.Throws; - -public class ExceptionWith( - IDelegateSource delegateSource, - Func exceptionSelector, - [CallerMemberName] string callerMemberName = "") -{ - protected AssertionBuilder AssertionBuilder { get; } = delegateSource.AssertionBuilder - .AppendExpression(callerMemberName); - - public ExceptionWithMessage Message => - new(delegateSource, exceptionSelector); - - public ThrowsException InnerException => new(delegateSource, e => exceptionSelector(e)?.InnerException); -} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Throws/ExceptionWithMessage.cs b/TUnit.Assertions/AssertConditions/Throws/ExceptionWithMessage.cs deleted file mode 100644 index b7cf7d4b86..0000000000 --- a/TUnit.Assertions/AssertConditions/Throws/ExceptionWithMessage.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Runtime.CompilerServices; -using TUnit.Assertions.AssertConditions.Interfaces; -using TUnit.Assertions.AssertionBuilders; -using TUnit.Assertions.Extensions; - -namespace TUnit.Assertions.AssertConditions.Throws; - -public class ExceptionWithMessage( - IDelegateSource delegateSource, - Func exceptionSelector) -{ - protected AssertionBuilder AssertionBuilder { get; } = delegateSource.AssertionBuilder - .AppendExpression("Message"); - - public InvokableDelegateAssertionBuilder EqualTo(string expected, [CallerArgumentExpression("expected")] string doNotPopulateThisValue = "") - { - return EqualTo(expected, StringComparison.Ordinal, doNotPopulateThisValue); - } - - public InvokableDelegateAssertionBuilder EqualTo(string expected, StringComparison stringComparison, [CallerArgumentExpression("expected")] string doNotPopulateThisValue1 = "", [CallerArgumentExpression("stringComparison")] string doNotPopulateThisValue2 = "") - { - return delegateSource.RegisterAssertion(new ThrowsWithMessageEqualToExpectedValueAssertCondition(expected, stringComparison, exceptionSelector) - , [doNotPopulateThisValue1, doNotPopulateThisValue2]); - } - - public InvokableDelegateAssertionBuilder Containing(string expected, [CallerArgumentExpression("expected")] string doNotPopulateThisValue = "") - { - return Containing(expected, StringComparison.Ordinal, doNotPopulateThisValue); - } - - public InvokableDelegateAssertionBuilder Containing(string expected, StringComparison stringComparison, [CallerArgumentExpression("expected")] string doNotPopulateThisValue1 = "", [CallerArgumentExpression("stringComparison")] string doNotPopulateThisValue2 = "") - { - return delegateSource.RegisterAssertion(new ThrowsWithMessageContainingExpectedValueAssertCondition(expected, stringComparison, exceptionSelector) - , [doNotPopulateThisValue1, doNotPopulateThisValue2]); - } -} \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Throws/ThrowsAnythingExpectedValueAssertCondition.cs b/TUnit.Assertions/AssertConditions/Throws/ThrowsAnyExceptionAssertCondition.cs similarity index 85% rename from TUnit.Assertions/AssertConditions/Throws/ThrowsAnythingExpectedValueAssertCondition.cs rename to TUnit.Assertions/AssertConditions/Throws/ThrowsAnyExceptionAssertCondition.cs index 50770c6098..e1a4114a1e 100644 --- a/TUnit.Assertions/AssertConditions/Throws/ThrowsAnythingExpectedValueAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Throws/ThrowsAnyExceptionAssertCondition.cs @@ -1,6 +1,6 @@ namespace TUnit.Assertions.AssertConditions.Throws; -public class ThrowsAnythingExpectedValueAssertCondition +public class ThrowsAnyExceptionAssertCondition : DelegateAssertCondition { protected override string GetExpectation() diff --git a/TUnit.Assertions/AssertConditions/Throws/ThrowsExactTypeOfExpectedValueAssertCondition.cs b/TUnit.Assertions/AssertConditions/Throws/ThrowsExactlyAssertCondition.cs similarity index 100% rename from TUnit.Assertions/AssertConditions/Throws/ThrowsExactTypeOfExpectedValueAssertCondition.cs rename to TUnit.Assertions/AssertConditions/Throws/ThrowsExactlyAssertCondition.cs diff --git a/TUnit.Assertions/AssertConditions/Throws/ThrowsException.cs b/TUnit.Assertions/AssertConditions/Throws/ThrowsException.cs index 1514828404..7fd8c39bff 100644 --- a/TUnit.Assertions/AssertConditions/Throws/ThrowsException.cs +++ b/TUnit.Assertions/AssertConditions/Throws/ThrowsException.cs @@ -5,46 +5,42 @@ namespace TUnit.Assertions.AssertConditions.Throws; -public class ThrowsException +public class ThrowsException( + InvokableDelegateAssertionBuilder delegateAssertionBuilder, + IDelegateSource delegateSource, + Func exceptionSelector, + [CallerMemberName] string callerMemberName = "") + where TException : Exception { - private readonly IDelegateSource _delegateSource; - private readonly Func _exceptionSelector; + private readonly IDelegateSource _delegateSource = delegateSource; + private readonly Func _exceptionSelector = exceptionSelector; + private IDelegateSource delegateSource; + private Func exceptionSelector; - public ThrowsException(IDelegateSource delegateSource, - Func exceptionSelector, - [CallerMemberName] string callerMemberName = "") + public ThrowsException WithMessageMatching(StringMatcher match, [CallerArgumentExpression("match")] string doNotPopulateThisValue = "") { - _delegateSource = delegateSource; - _exceptionSelector = exceptionSelector; - - AssertionBuilder = delegateSource.AssertionBuilder - .AppendExpression(callerMemberName); + _delegateSource.RegisterAssertion(new ThrowsWithMessageMatchingAssertCondition(match, _exceptionSelector) + , [doNotPopulateThisValue]); + return this; } - protected AssertionBuilder AssertionBuilder { get; } - - public ExceptionWith With => new(_delegateSource, _exceptionSelector); - - public InvokableDelegateAssertionBuilder OfAnyType() => - _delegateSource.RegisterAssertion(new ThrowsAnythingExpectedValueAssertCondition() - , []); - - public InvokableDelegateAssertionBuilder OfType() where TExpected : Exception => _delegateSource.RegisterAssertion(new ThrowsExactTypeOfDelegateAssertCondition() - , [typeof(TExpected).Name]); + public ThrowsException WithMessage(string expected, [CallerArgumentExpression("expected")] string doNotPopulateThisValue = "") + { + _delegateSource.RegisterAssertion(new ThrowsWithMessageAssertCondition(expected, StringComparison.Ordinal, _exceptionSelector) + , [doNotPopulateThisValue]); + return this; + } - public InvokableDelegateAssertionBuilder SubClassOf() => - _delegateSource.RegisterAssertion(new ThrowsSubClassOfExpectedValueAssertCondition() - , [typeof(TExpected).Name]); - - public InvokableDelegateAssertionBuilder WithCustomCondition(Func action, Func messageFactory, [CallerArgumentExpression("action")] string expectedExpression = "") => - _delegateSource.RegisterAssertion(new FuncValueAssertCondition(default, - (_, exception, _) => action(_exceptionSelector(exception)), - (_, exception, _) => messageFactory(_exceptionSelector(exception)) - ), [expectedExpression]); + public ThrowsException WithInnerException() + { + _delegateSource.AssertionBuilder.AppendExpression($"{nameof(WithInnerException)}()"); + return new(delegateAssertionBuilder, _delegateSource, e => _exceptionSelector(e)?.InnerException); + } - public TaskAwaiter GetAwaiter() + public TaskAwaiter GetAwaiter() { - Task task = OfAnyType().ProcessAssertionsAsync(); + var task = delegateAssertionBuilder.ProcessAssertionsAsync( + d => d.Exception as TException); return task.GetAwaiter(); } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Throws/ThrowsNothingExpectedValueAssertCondition.cs b/TUnit.Assertions/AssertConditions/Throws/ThrowsNothingAssertCondition.cs similarity index 74% rename from TUnit.Assertions/AssertConditions/Throws/ThrowsNothingExpectedValueAssertCondition.cs rename to TUnit.Assertions/AssertConditions/Throws/ThrowsNothingAssertCondition.cs index f8f6d2a958..a5eb52bac1 100644 --- a/TUnit.Assertions/AssertConditions/Throws/ThrowsNothingExpectedValueAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Throws/ThrowsNothingAssertCondition.cs @@ -2,7 +2,7 @@ namespace TUnit.Assertions.AssertConditions.Throws; -public class ThrowsNothingExpectedValueAssertCondition : DelegateAssertCondition +public class ThrowsNothingAssertCondition : DelegateAssertCondition { protected override string GetExpectation() => "to throw nothing"; @@ -11,5 +11,5 @@ protected override Task GetResult(TActual? actualValue, Excepti => AssertionResult .FailIf( () => exception is not null, - $"{exception?.GetType().Name.PrependAOrAn()} was thrown"); + $"{exception?.GetType().Name.PrependAOrAn()} was thrown:{Environment.NewLine}{exception?.Message}"); } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Throws/ThrowsSubClassOfExpectedValueAssertCondition.cs b/TUnit.Assertions/AssertConditions/Throws/ThrowsOfTypeAssertCondition.cs similarity index 70% rename from TUnit.Assertions/AssertConditions/Throws/ThrowsSubClassOfExpectedValueAssertCondition.cs rename to TUnit.Assertions/AssertConditions/Throws/ThrowsOfTypeAssertCondition.cs index 00a2ad17df..0a9089b190 100644 --- a/TUnit.Assertions/AssertConditions/Throws/ThrowsSubClassOfExpectedValueAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Throws/ThrowsOfTypeAssertCondition.cs @@ -2,7 +2,7 @@ namespace TUnit.Assertions.AssertConditions.Throws; -public class ThrowsSubClassOfExpectedValueAssertCondition : DelegateAssertCondition +public class ThrowsOfTypeAssertCondition : DelegateAssertCondition { protected override string GetExpectation() => $"to throw {typeof(TExpectedException).Name.PrependAOrAn()}"; @@ -13,7 +13,7 @@ protected override Task GetResult(TActual? actualValue, Excepti () => exception is null, "none was thrown") .OrFailIf( - () => !exception!.GetType().IsSubclassOf(typeof(TExpectedException)), + () => !exception!.GetType().IsAssignableTo(typeof(TExpectedException)), $"{exception?.GetType().Name.PrependAOrAn()} was thrown" ); } \ No newline at end of file diff --git a/TUnit.Assertions/AssertConditions/Throws/ThrowsWithMessageEqualToExpectedValueAssertCondition.cs b/TUnit.Assertions/AssertConditions/Throws/ThrowsWithMessageAssertCondition.cs similarity index 78% rename from TUnit.Assertions/AssertConditions/Throws/ThrowsWithMessageEqualToExpectedValueAssertCondition.cs rename to TUnit.Assertions/AssertConditions/Throws/ThrowsWithMessageAssertCondition.cs index 5c13ea8b16..2312f7cf52 100644 --- a/TUnit.Assertions/AssertConditions/Throws/ThrowsWithMessageEqualToExpectedValueAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Throws/ThrowsWithMessageAssertCondition.cs @@ -3,14 +3,15 @@ namespace TUnit.Assertions.AssertConditions.Throws; -public class ThrowsWithMessageEqualToExpectedValueAssertCondition( +public class ThrowsWithMessageAssertCondition( string expectedMessage, StringComparison stringComparison, Func exceptionSelector) : DelegateAssertCondition + where TException : Exception { protected override string GetExpectation() - => $"to have Message equal to \"{expectedMessage.ShowNewLines().TruncateWithEllipsis(100)}\""; + => $"to throw {typeof(TException).Name.PrependAOrAn()} which message equals \"{expectedMessage.ShowNewLines().TruncateWithEllipsis(100)}\""; protected override Task GetResult(TActual? actualValue, Exception? exception) { diff --git a/TUnit.Assertions/AssertConditions/Throws/ThrowsWithMessageContainingExpectedValueAssertCondition.cs b/TUnit.Assertions/AssertConditions/Throws/ThrowsWithMessageMatchingAssertCondition.cs similarity index 52% rename from TUnit.Assertions/AssertConditions/Throws/ThrowsWithMessageContainingExpectedValueAssertCondition.cs rename to TUnit.Assertions/AssertConditions/Throws/ThrowsWithMessageMatchingAssertCondition.cs index 2bcac0fd6f..95648d2bf1 100644 --- a/TUnit.Assertions/AssertConditions/Throws/ThrowsWithMessageContainingExpectedValueAssertCondition.cs +++ b/TUnit.Assertions/AssertConditions/Throws/ThrowsWithMessageMatchingAssertCondition.cs @@ -1,13 +1,15 @@ +using TUnit.Assertions.Extensions; + namespace TUnit.Assertions.AssertConditions.Throws; -public class ThrowsWithMessageContainingExpectedValueAssertCondition( - string expectedMessage, - StringComparison stringComparison, +public class ThrowsWithMessageMatchingAssertCondition( + StringMatcher match, Func exceptionSelector) : DelegateAssertCondition + where TException : Exception { protected override string GetExpectation() - => $"to have Message containing \"{expectedMessage}\""; + => $"to throw {typeof(TException).Name.PrependAOrAn()} which message matches \"{match.ToString()?.ShowNewLines().TruncateWithEllipsis(100)}\""; protected override Task GetResult(TActual? actualValue, Exception? exception) { @@ -18,7 +20,7 @@ protected override Task GetResult(TActual? actualValue, Excepti () => actualException is null, "the exception is null") .OrFailIf( - () => !actualException!.Message.Contains(expectedMessage, stringComparison), - "it was not found"); + () => !match.Matches(actualException!.Message), + $"found \"{actualException!.Message.ShowNewLines().TruncateWithEllipsis(100)}\""); } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertionBuilders/AsyncDelegateAssertionBuilder.cs b/TUnit.Assertions/AssertionBuilders/AsyncDelegateAssertionBuilder.cs index 2661109d1d..6aa0996cec 100644 --- a/TUnit.Assertions/AssertionBuilders/AsyncDelegateAssertionBuilder.cs +++ b/TUnit.Assertions/AssertionBuilders/AsyncDelegateAssertionBuilder.cs @@ -1,4 +1,6 @@ using TUnit.Assertions.AssertConditions.Interfaces; +using TUnit.Assertions.AssertConditions.Operators; +using TUnit.Assertions.AssertConditions.Throws; using TUnit.Assertions.Extensions; namespace TUnit.Assertions.AssertionBuilders; @@ -12,4 +14,24 @@ internal AsyncDelegateAssertionBuilder(Func function, string expressionBui } AssertionBuilder ISource.AssertionBuilder => this; + + public ThrowsException Throws() where TException : Exception + { + return new DelegateSource(this).Throws(); + } + + public ThrowsException ThrowsExactly() where TException : Exception + { + return new DelegateSource(this).ThrowsExactly(); + } + + public ThrowsException ThrowsException() + { + return new DelegateSource(this).ThrowsException(); + } + + public CastableAssertionBuilder ThrowsNothing() + { + return new DelegateSource(this).ThrowsNothing(); + } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertionBuilders/AsyncValueDelegateAssertionBuilder.cs b/TUnit.Assertions/AssertionBuilders/AsyncValueDelegateAssertionBuilder.cs index 6af605dbcb..30e5688bb6 100644 --- a/TUnit.Assertions/AssertionBuilders/AsyncValueDelegateAssertionBuilder.cs +++ b/TUnit.Assertions/AssertionBuilders/AsyncValueDelegateAssertionBuilder.cs @@ -1,5 +1,6 @@ using TUnit.Assertions.AssertConditions.Interfaces; using TUnit.Assertions.AssertConditions.Operators; +using TUnit.Assertions.AssertConditions.Throws; using TUnit.Assertions.Extensions; namespace TUnit.Assertions.AssertionBuilders; @@ -12,7 +13,27 @@ public class AsyncValueDelegateAssertionBuilder internal AsyncValueDelegateAssertionBuilder(Func> function, string expressionBuilder) : base(function.AsAssertionData(expressionBuilder), expressionBuilder) { } - + + public ThrowsException Throws() where TException : Exception + { + return new DelegateSource(this).Throws(); + } + + public ThrowsException ThrowsExactly() where TException : Exception + { + return new DelegateSource(this).ThrowsExactly(); + } + + public ThrowsException ThrowsException() + { + return new DelegateSource(this).ThrowsException(); + } + + public CastableAssertionBuilder ThrowsNothing() + { + return new DelegateSource(this).ThrowsNothing(); + } + AssertionBuilder ISource.AssertionBuilder => this; public InvokableValueAssertionBuilder IsTypeOf(Type type) { diff --git a/TUnit.Assertions/AssertionBuilders/CastableAssertionBuilder.cs b/TUnit.Assertions/AssertionBuilders/CastableAssertionBuilder.cs index 7c81f9688c..48a8132865 100644 --- a/TUnit.Assertions/AssertionBuilders/CastableAssertionBuilder.cs +++ b/TUnit.Assertions/AssertionBuilders/CastableAssertionBuilder.cs @@ -4,18 +4,31 @@ namespace TUnit.Assertions.AssertionBuilders; public class CastableAssertionBuilder : InvokableValueAssertionBuilder { + private readonly Func, TExpected?> _mapper; + internal CastableAssertionBuilder(InvokableAssertionBuilder assertionBuilder) : base(assertionBuilder) { + _mapper = DefaultMapper; + } + + internal CastableAssertionBuilder(InvokableAssertionBuilder assertionBuilder, Func, TExpected?> mapper) : base(assertionBuilder) + { + _mapper = mapper; } - + public new TaskAwaiter GetAwaiter() { return AssertType().GetAwaiter(); } - private async Task AssertType() + private static TExpected? DefaultMapper(AssertionData data) + { + return (TExpected)Convert.ChangeType(data.Result, typeof(TExpected)); + } + + private async Task AssertType() { var data = await ProcessAssertionsAsync(); - return (TExpected) Convert.ChangeType(data.Result, typeof(TExpected))!; + return _mapper(data); } } \ No newline at end of file diff --git a/TUnit.Assertions/AssertionBuilders/DelegateAssertionBuilder.cs b/TUnit.Assertions/AssertionBuilders/DelegateAssertionBuilder.cs index 6f04f4485b..3f2dd21a59 100644 --- a/TUnit.Assertions/AssertionBuilders/DelegateAssertionBuilder.cs +++ b/TUnit.Assertions/AssertionBuilders/DelegateAssertionBuilder.cs @@ -1,4 +1,6 @@ using TUnit.Assertions.AssertConditions.Interfaces; +using TUnit.Assertions.AssertConditions.Operators; +using TUnit.Assertions.AssertConditions.Throws; using TUnit.Assertions.Extensions; namespace TUnit.Assertions.AssertionBuilders; @@ -10,6 +12,25 @@ public class DelegateAssertionBuilder internal DelegateAssertionBuilder(Action action, string expressionBuilder) : base(action.AsAssertionData(expressionBuilder), expressionBuilder) { } + public ThrowsException Throws() where TException : Exception + { + return new DelegateSource(this).Throws(); + } + + public ThrowsException ThrowsExactly() where TException : Exception + { + return new DelegateSource(this).ThrowsExactly(); + } + + public ThrowsException ThrowsException() + { + return new DelegateSource(this).ThrowsException(); + } + + public CastableAssertionBuilder ThrowsNothing() + { + return new DelegateSource(this).ThrowsNothing(); + } AssertionBuilder ISource.AssertionBuilder => this; } \ No newline at end of file diff --git a/TUnit.Assertions/AssertionBuilders/InvokableAssertionBuilder.cs b/TUnit.Assertions/AssertionBuilders/InvokableAssertionBuilder.cs index 57dc9edc6e..5c662bee01 100644 --- a/TUnit.Assertions/AssertionBuilders/InvokableAssertionBuilder.cs +++ b/TUnit.Assertions/AssertionBuilders/InvokableAssertionBuilder.cs @@ -16,6 +16,12 @@ internal InvokableAssertionBuilder(AssertionBuilder assertionBuilder) : } } + internal async Task ProcessAssertionsAsync(Func, T> mapper) + { + var assertionData = await ProcessAssertionsAsync(); + return mapper(assertionData); + } + internal async Task> ProcessAssertionsAsync() { _invokedAssertionData ??= await AssertionDataDelegate(); diff --git a/TUnit.Assertions/AssertionBuilders/ValueDelegateAssertionBuilder.cs b/TUnit.Assertions/AssertionBuilders/ValueDelegateAssertionBuilder.cs index a359bd6c27..c7c44ff2af 100644 --- a/TUnit.Assertions/AssertionBuilders/ValueDelegateAssertionBuilder.cs +++ b/TUnit.Assertions/AssertionBuilders/ValueDelegateAssertionBuilder.cs @@ -1,5 +1,6 @@ using TUnit.Assertions.AssertConditions.Interfaces; using TUnit.Assertions.AssertConditions.Operators; +using TUnit.Assertions.AssertConditions.Throws; using TUnit.Assertions.Extensions; namespace TUnit.Assertions.AssertionBuilders; @@ -12,9 +13,29 @@ public class ValueDelegateAssertionBuilder internal ValueDelegateAssertionBuilder(Func function, string expressionBuilder) : base(function.AsAssertionData(expressionBuilder), expressionBuilder) { } - + + public ThrowsException Throws() where TException : Exception + { + return new DelegateSource(this).Throws(); + } + + public ThrowsException ThrowsExactly() where TException : Exception + { + return new DelegateSource(this).ThrowsExactly(); + } + + public ThrowsException ThrowsException() + { + return new DelegateSource(this).ThrowsException(); + } + AssertionBuilder ISource.AssertionBuilder => this; - + + public CastableAssertionBuilder ThrowsNothing() + { + return new DelegateSource(this).ThrowsNothing(); + } + public InvokableValueAssertionBuilder IsTypeOf(Type type) { return new ValueSource(this).IsTypeOf(type); diff --git a/TUnit.Assertions/Extensions/Throws/ThrowsExtensions.cs b/TUnit.Assertions/Extensions/Throws/ThrowsExtensions.cs deleted file mode 100644 index ef191f9cb3..0000000000 --- a/TUnit.Assertions/Extensions/Throws/ThrowsExtensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -using TUnit.Assertions.AssertConditions.Interfaces; -using TUnit.Assertions.AssertConditions.Throws; -using TUnit.Assertions.AssertionBuilders; - -namespace TUnit.Assertions.Extensions; - -public static class ThrowsExtensions -{ - public static ThrowsException ThrowsException(this IDelegateSource delegateSource) - { - return new(delegateSource, exception => exception); - } - - public static InvokableDelegateAssertionBuilder ThrowsNothing(this IDelegateSource delegateSource) - { - return delegateSource.RegisterAssertion(new ThrowsNothingExpectedValueAssertCondition() - , []); - } -} \ No newline at end of file diff --git a/TUnit.TestProject/DependsOnTests2.cs b/TUnit.TestProject/DependsOnTests2.cs index 0801976773..473d25d8aa 100644 --- a/TUnit.TestProject/DependsOnTests2.cs +++ b/TUnit.TestProject/DependsOnTests2.cs @@ -27,7 +27,7 @@ public async Task Test2() [Test] public async Task Test3() { - await Assert.That(() => TestContext.Current!.GetTests(nameof(Test1))).ThrowsException().With.Message.EqualTo("Cannot get unfinished tests - Did you mean to add a [DependsOn] attribute?"); + await Assert.That(() => TestContext.Current!.GetTests(nameof(Test1))).ThrowsException().WithMessage("Cannot get unfinished tests - Did you mean to add a [DependsOn] attribute?"); } [After(Class)] diff --git a/TUnit.TestProject/Tests.cs b/TUnit.TestProject/Tests.cs index f855e248c2..6fd37f8a33 100644 --- a/TUnit.TestProject/Tests.cs +++ b/TUnit.TestProject/Tests.cs @@ -203,7 +203,7 @@ public async Task TestContext2() [Category("Fail")] public async Task Throws1() { - await Assert.That(() => new string([])).ThrowsException().OfAnyType(); + await Assert.That(() => new string([])).ThrowsException(); } [Test] @@ -213,14 +213,14 @@ public async Task Throws2() await Assert.That(async () => { await Task.Yield(); - }).ThrowsException().OfAnyType(); + }).ThrowsException(); } [Test] [Category("Pass")] public async Task Throws3() { - await Assert.That(() => throw new ApplicationException()).ThrowsException().OfAnyType(); + await Assert.That(() => throw new ApplicationException()).ThrowsException(); } [Test]