From e91bc0d90080e507c6fe96ba012a0f1760e6fc38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Breu=C3=9F=20Valentin?= Date: Fri, 19 Sep 2025 15:59:29 +0200 Subject: [PATCH 1/2] feat: add `WithoutMessage` for delegate assertions --- Docs/pages/docs/expectations/06-delegates.md | 7 +- .../ThatDelegateThrows.WithMessage.cs | 22 ++ ...hatDelegateThrows.WithMessageContaining.cs | 23 ++ .../Exceptions/ThatException.HasMessage.cs | 24 +- .../ThatException.HasMessageContaining.cs | 46 +++- ...Delegate.ThrowsException.WithInnerTests.cs | 6 + ...te.ThrowsException.WithoutMessage.Tests.cs | 115 +++++++++ ...xception.WithoutMessageContaining.Tests.cs | 183 ++++++++++++++ .../ThatException.DoesNotHaveMessage.Tests.cs | 115 +++++++++ ...tion.DoesNotHaveMessageContaining.Tests.cs | 232 ++++++++++++++++++ .../ThatException.HasInner.GenericTests.cs | 6 + .../ThatException.HasInner.TypeTests.cs | 6 + .../ThatException.HasMessage.Tests.cs | 3 + ...hatException.HasMessageContaining.Tests.cs | 3 + 14 files changed, 772 insertions(+), 19 deletions(-) create mode 100644 Tests/aweXpect.Tests/Delegates/ThatDelegate.ThrowsException.WithoutMessage.Tests.cs create mode 100644 Tests/aweXpect.Tests/Delegates/ThatDelegate.ThrowsException.WithoutMessageContaining.Tests.cs create mode 100644 Tests/aweXpect.Tests/Exceptions/ThatException.DoesNotHaveMessage.Tests.cs create mode 100644 Tests/aweXpect.Tests/Exceptions/ThatException.DoesNotHaveMessageContaining.Tests.cs diff --git a/Docs/pages/docs/expectations/06-delegates.md b/Docs/pages/docs/expectations/06-delegates.md index 6fe207d1b..9ec563b0e 100644 --- a/Docs/pages/docs/expectations/06-delegates.md +++ b/Docs/pages/docs/expectations/06-delegates.md @@ -83,9 +83,12 @@ This is especially useful with parametrized tests where it depends on a paramete You can verify the message of the thrown exception: ```csharp -void Act() => throw new CustomException("my exception"); +void Act() => throw new CustomException("This is my exception text"); -await Expect.That(Act).ThrowsException().WithMessage("my exception"); +await Expect.That(Act).ThrowsException().WithMessage("This is my exception text"); +await Expect.That(Act).ThrowsException().WithoutMessage("some other text"); +await Expect.That(Act).ThrowsException().WithMessageContaining("my exception"); +await Expect.That(Act).ThrowsException().WithoutMessageContaining("something else"); ``` You can use the same configuration options as when [comparing strings](/docs/expectations/string#equality). diff --git a/Source/aweXpect/That/Delegates/ThatDelegateThrows.WithMessage.cs b/Source/aweXpect/That/Delegates/ThatDelegateThrows.WithMessage.cs index 28d898c22..7c4bdbd38 100644 --- a/Source/aweXpect/That/Delegates/ThatDelegateThrows.WithMessage.cs +++ b/Source/aweXpect/That/Delegates/ThatDelegateThrows.WithMessage.cs @@ -1,5 +1,6 @@ using System; using aweXpect.Core; +using aweXpect.Core.Constraints; using aweXpect.Delegates; using aweXpect.Options; using aweXpect.Results; @@ -28,4 +29,25 @@ public static StringEqualityTypeResult + /// Verifies that the thrown exception does not have a message equal to . + /// + public static StringEqualityTypeResult> WithoutMessage( + this ThatDelegateThrows source, + string unexpected) + where TException : Exception? + { + StringEqualityOptions options = new(); + return new StringEqualityTypeResult>( + source.ExpectationBuilder.AddConstraint((it, grammars) + => new ThatException.HasMessageValueConstraint( + source.ExpectationBuilder, + it, + grammars | ExpectationGrammars.Active | ExpectationGrammars.Nested, + unexpected, + options).Invert()), + source, + options); + } } diff --git a/Source/aweXpect/That/Delegates/ThatDelegateThrows.WithMessageContaining.cs b/Source/aweXpect/That/Delegates/ThatDelegateThrows.WithMessageContaining.cs index c1dffa7de..57e97073c 100644 --- a/Source/aweXpect/That/Delegates/ThatDelegateThrows.WithMessageContaining.cs +++ b/Source/aweXpect/That/Delegates/ThatDelegateThrows.WithMessageContaining.cs @@ -1,5 +1,6 @@ using System; using aweXpect.Core; +using aweXpect.Core.Constraints; using aweXpect.Delegates; using aweXpect.Options; using aweXpect.Results; @@ -28,4 +29,26 @@ public static StringEqualityResult> W source, options); } + + /// + /// Verifies that the thrown exception does not have a message that contains the + /// pattern. + /// + public static StringEqualityResult> WithoutMessageContaining( + this ThatDelegateThrows source, + string? unexpected) + where TException : Exception? + { + StringEqualityOptions options = new(); + return new StringEqualityResult>( + source.ExpectationBuilder.AddConstraint((it, grammars) + => new ThatException.HasMessageContainingConstraint( + source.ExpectationBuilder, + it, + grammars | ExpectationGrammars.Active | ExpectationGrammars.Nested, + unexpected, + options).Invert()), + source, + options); + } } diff --git a/Source/aweXpect/That/Exceptions/ThatException.HasMessage.cs b/Source/aweXpect/That/Exceptions/ThatException.HasMessage.cs index a927207e1..dd435e0af 100644 --- a/Source/aweXpect/That/Exceptions/ThatException.HasMessage.cs +++ b/Source/aweXpect/That/Exceptions/ThatException.HasMessage.cs @@ -27,23 +27,39 @@ public static partial class ThatException options); } + /// + /// Verifies that the actual exception does not have a message equal to . + /// + public static StringEqualityTypeResult> DoesNotHaveMessage( + this IThat source, + string unexpected) + { + StringEqualityOptions options = new(); + return new StringEqualityTypeResult>( + source.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars) + => new HasMessageValueConstraint( + expectationBuilder, it, grammars, unexpected, options).Invert()), + source, + options); + } + internal class HasMessageValueConstraint( ExpectationBuilder expectationBuilder, string it, ExpectationGrammars grammars, string expected, StringEqualityOptions options) - : ConstraintResult.WithValue(grammars), + : ConstraintResult.WithNotNullValue(it, grammars), IAsyncConstraint { public async Task IsMetBy(Exception? actual, CancellationToken cancellationToken) { Actual = actual; Outcome = await options.AreConsideredEqual(actual?.Message, expected) ? Outcome.Success : Outcome.Failure; - if (Outcome == Outcome.Failure) + if (!string.IsNullOrEmpty(actual?.Message)) { expectationBuilder.UpdateContexts(contexts => contexts - .Add(new ResultContext("Message", actual?.Message))); + .Add(new ResultContext("Message", actual.Message))); } return this; @@ -70,7 +86,7 @@ protected override void AppendNormalExpectation(StringBuilder stringBuilder, str } protected override void AppendNormalResult(StringBuilder stringBuilder, string? indentation = null) - => stringBuilder.Append(options.GetExtendedFailure(it, Grammars, Actual?.Message, expected)); + => stringBuilder.Append(options.GetExtendedFailure(It, Grammars, Actual?.Message, expected)); protected override void AppendNegatedExpectation(StringBuilder stringBuilder, string? indentation = null) => AppendNormalExpectation(stringBuilder, indentation); diff --git a/Source/aweXpect/That/Exceptions/ThatException.HasMessageContaining.cs b/Source/aweXpect/That/Exceptions/ThatException.HasMessageContaining.cs index aadc3c600..ea7738585 100644 --- a/Source/aweXpect/That/Exceptions/ThatException.HasMessageContaining.cs +++ b/Source/aweXpect/That/Exceptions/ThatException.HasMessageContaining.cs @@ -27,13 +27,29 @@ public static partial class ThatException options); } + /// + /// Verifies that the actual exception does not have a message containing the pattern. + /// + public static StringEqualityTypeResult> DoesNotHaveMessageContaining( + this IThat source, + string? unexpected) + { + StringEqualityOptions options = new(); + return new StringEqualityTypeResult>( + source.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars) + => new HasMessageContainingConstraint( + expectationBuilder, it, grammars, unexpected, options).Invert()), + source, + options); + } + internal class HasMessageContainingConstraint( ExpectationBuilder expectationBuilder, string it, ExpectationGrammars grammars, string? expected, StringEqualityOptions options) - : ConstraintResult.WithValue(grammars), + : ConstraintResult.WithNotNullValue(it, grammars), IAsyncConstraint { public async Task IsMetBy(Exception? actual, CancellationToken cancellationToken) @@ -43,10 +59,10 @@ public async Task IsMetBy(Exception? actual, CancellationToken Outcome = expected is null || await options.AreConsideredEqual(actual?.Message, $"*{expected}*") ? Outcome.Success : Outcome.Failure; - if (Outcome == Outcome.Failure) + if (!string.IsNullOrEmpty(actual?.Message)) { expectationBuilder.UpdateContexts(contexts => contexts - .Add(new ResultContext("Message", actual?.Message))); + .Add(new ResultContext("Message", actual.Message))); } return this; @@ -57,42 +73,46 @@ protected override void AppendNormalExpectation(StringBuilder stringBuilder, str ExpectationGrammars equalityGrammars = Grammars; if (Grammars.HasFlag(ExpectationGrammars.Active)) { - stringBuilder.Append("with Message containing "); + stringBuilder.Append("with Message containing matching "); equalityGrammars &= ~ExpectationGrammars.Active; } else if (Grammars.HasFlag(ExpectationGrammars.Nested)) { - stringBuilder.Append("Message contains "); + stringBuilder.Append("Message contains matching "); } else { - stringBuilder.Append("contains Message "); + stringBuilder.Append("contains Message matching "); } - - stringBuilder.Append(options.GetExpectation(expected, equalityGrammars)); + + options.Exactly(); + stringBuilder.Append(options.GetExpectation(expected, equalityGrammars)["equal to ".Length..]); + options.AsWildcard(); } protected override void AppendNormalResult(StringBuilder stringBuilder, string? indentation = null) - => stringBuilder.Append(options.GetExtendedFailure(it, Grammars, Actual?.Message, expected)); + => stringBuilder.Append(options.GetExtendedFailure(It, Grammars, Actual?.Message, expected)); protected override void AppendNegatedExpectation(StringBuilder stringBuilder, string? indentation = null) { ExpectationGrammars equalityGrammars = Grammars; if (Grammars.HasFlag(ExpectationGrammars.Active)) { - stringBuilder.Append("with Message not containing "); + stringBuilder.Append("with Message not containing matching "); equalityGrammars &= ~ExpectationGrammars.Active; } else if (Grammars.HasFlag(ExpectationGrammars.Nested)) { - stringBuilder.Append("Message does not contain "); + stringBuilder.Append("Message does not contain matching "); } else { - stringBuilder.Append("does not contain Message "); + stringBuilder.Append("does not contain Message matching "); } - stringBuilder.Append(options.GetExpectation(expected, equalityGrammars.Negate())); + options.Exactly(); + stringBuilder.Append(options.GetExpectation(expected, equalityGrammars.Negate())["equal to ".Length..]); + options.AsWildcard(); } protected override void AppendNegatedResult(StringBuilder stringBuilder, string? indentation = null) diff --git a/Tests/aweXpect.Tests/Delegates/ThatDelegate.ThrowsException.WithInnerTests.cs b/Tests/aweXpect.Tests/Delegates/ThatDelegate.ThrowsException.WithInnerTests.cs index 34134b3bb..7e52645d6 100644 --- a/Tests/aweXpect.Tests/Delegates/ThatDelegate.ThrowsException.WithInnerTests.cs +++ b/Tests/aweXpect.Tests/Delegates/ThatDelegate.ThrowsException.WithInnerTests.cs @@ -222,6 +222,9 @@ Expected that action throws an exception with an inner MyException whose Message is equal to "foo", but it was a ThatDelegate.CustomException: foo + + Message: + foo """); } @@ -480,6 +483,9 @@ Expected that action throws an exception with an inner MyException whose Message is equal to "foo", but it was a ThatDelegate.CustomException: foo + + Message: + foo """); } diff --git a/Tests/aweXpect.Tests/Delegates/ThatDelegate.ThrowsException.WithoutMessage.Tests.cs b/Tests/aweXpect.Tests/Delegates/ThatDelegate.ThrowsException.WithoutMessage.Tests.cs new file mode 100644 index 000000000..12029da3e --- /dev/null +++ b/Tests/aweXpect.Tests/Delegates/ThatDelegate.ThrowsException.WithoutMessage.Tests.cs @@ -0,0 +1,115 @@ +namespace aweXpect.Tests; + +public sealed partial class ThatDelegate +{ + public sealed partial class ThrowsException + { + public sealed class WithoutMessage + { + public sealed class Tests + { + [Fact] + public async Task CanCompareCaseInsensitive() + { + string message = "FOO"; + Exception exception = + new OuterException(message, new CustomException()); + void Delegate() => throw exception; + + async Task Act() + => await That(Delegate).ThrowsException() + .WithoutMessage("foo").IgnoringCase(); + + await That(Act).Throws() + .WithMessage(""" + Expected that Delegate + throws an exception with Message not equal to "foo" ignoring case, + but it was "FOO" + + Message: + FOO + """); + } + + [Fact] + public async Task CanUseWildcardCheck() + { + string message = "foo-bar"; + Exception exception = + new OuterException(message, new CustomException()); + void Delegate() => throw exception; + + async Task Act() + => await That(Delegate).ThrowsException() + .WithoutMessage("foo*").AsWildcard(); + + await That(Act).Throws() + .WithMessage(""" + Expected that Delegate + throws an exception with Message not matching "foo*", + but it was "foo-bar" + + Message: + foo-bar + """); + } + + [Fact] + public async Task ShouldCompareCaseSensitive() + { + string message = "FOO"; + Exception exception = + new OuterException(message, new CustomException()); + void Delegate() => throw exception; + + async Task Act() + => await That(Delegate).ThrowsException() + .WithoutMessage("foo"); + + await That(Act).DoesNotThrow(); + } + + [Fact] + public async Task ShouldIncludeExceptionType() + { + string message = "FOO"; + Exception exception = new CustomException(message); + void Delegate() => throw exception; + + async Task Act() + => await That(Delegate).Throws() + .WithoutMessage("foo"); + + await That(Act).DoesNotThrow(); + } + + [Theory] + [AutoData] + public async Task WhenAwaited_ShouldReturnThrownException(string message) + { + Exception exception = + new OuterException(message, new CustomException()); + void Delegate() => throw exception; + + Exception result = await That(Delegate) + .ThrowsException().WithoutMessage("foo"); + + await That(result).IsSameAs(exception); + } + + [Fact] + public async Task WhenMessagesAreDifferent_ShouldFail() + { + string actual = "actual text"; + string expected = "expected other text"; + Action action = () => throw new CustomException(actual); + + async Task Act() + => await That(action).ThrowsException().WithoutMessage(expected); + + await That(Act).DoesNotThrow(); + } + } + } + } +} diff --git a/Tests/aweXpect.Tests/Delegates/ThatDelegate.ThrowsException.WithoutMessageContaining.Tests.cs b/Tests/aweXpect.Tests/Delegates/ThatDelegate.ThrowsException.WithoutMessageContaining.Tests.cs new file mode 100644 index 000000000..e78eb758e --- /dev/null +++ b/Tests/aweXpect.Tests/Delegates/ThatDelegate.ThrowsException.WithoutMessageContaining.Tests.cs @@ -0,0 +1,183 @@ +namespace aweXpect.Tests; + +public sealed partial class ThatDelegate +{ + public sealed partial class ThrowsException + { + public sealed class WithoutMessageContaining + { + public sealed class Tests + { + [Fact] + public async Task CanCompareCaseInsensitive() + { + string message = "_FOO_BAR"; + Exception exception = + new OuterException(message, new CustomException()); + void Delegate() => throw exception; + + async Task Act() + => await That(Delegate).ThrowsException() + .WithoutMessageContaining("foo").IgnoringCase(); + + await That(Act).Throws() + .WithMessage(""" + Expected that Delegate + throws an exception with Message not containing matching "foo" ignoring case, + but it was "_FOO_BAR" + + Message: + _FOO_BAR + """); + } + + [Fact] + public async Task CanUseWildcardCheck() + { + string message = "_foo-BAR"; + Exception exception = + new OuterException(message, new CustomException()); + void Delegate() => throw exception; + + async Task Act() + => await That(Delegate).ThrowsException() + .WithoutMessageContaining("f?o-"); + + await That(Act).Throws() + .WithMessage(""" + Expected that Delegate + throws an exception with Message not containing matching "f?o-", + but it was "_foo-BAR" + + Message: + _foo-BAR + """); + } + + [Fact] + public async Task ShouldCompareCaseSensitive() + { + string message = "FOO"; + Exception exception = + new OuterException(message, new CustomException()); + void Delegate() => throw exception; + + async Task Act() + => await That(Delegate).ThrowsException() + .WithoutMessageContaining("foo"); + + await That(Act).DoesNotThrow(); + } + + [Fact] + public async Task ShouldIgnorePrecedingText() + { + string message = "some text before foo"; + Exception exception = + new OuterException(message, new CustomException()); + void Delegate() => throw exception; + + async Task Act() + => await That(Delegate).ThrowsException() + .WithoutMessageContaining("foo"); + + await That(Act).Throws() + .WithMessage(""" + Expected that Delegate + throws an exception with Message not containing matching "foo", + but it was "some text before foo" + + Message: + some text before foo + """); + } + + [Fact] + public async Task ShouldIgnoreSucceedingText() + { + string message = "foo and some other text"; + Exception exception = + new OuterException(message, new CustomException()); + void Delegate() => throw exception; + + async Task Act() + => await That(Delegate).ThrowsException() + .WithoutMessageContaining("foo"); + + await That(Act).Throws() + .WithMessage(""" + Expected that Delegate + throws an exception with Message not containing matching "foo", + but it was "foo and some other text" + + Message: + foo and some other text + """); + } + + [Fact] + public async Task ShouldIncludeExceptionType() + { + string message = "FOO"; + Exception exception = new CustomException(message); + void Delegate() => throw exception; + + async Task Act() + => await That(Delegate).Throws() + .WithoutMessageContaining("foo"); + + await That(Act).DoesNotThrow(); + } + + [Theory] + [AutoData] + public async Task WhenAwaited_ShouldReturnThrownException(string message) + { + Exception exception = + new OuterException(message, new CustomException()); + void Delegate() => throw exception; + + Exception result = await That(Delegate) + .ThrowsException().WithoutMessageContaining("foo"); + + await That(result).IsSameAs(exception); + } + + [Theory] + [AutoData] + public async Task WhenExpectedIsNull_ShouldFail(string message) + { + Exception exception = new(message); + void Delegate() => throw exception; + + async Task Act() + => await That(Delegate).ThrowsException() + .WithoutMessageContaining(null); + + await That(Act).Throws() + .WithMessage($""" + Expected that Delegate + throws an exception with Message not containing matching , + but it was "{message}" + + Message: + {message} + """); + } + + [Fact] + public async Task WhenMessagesAreDifferent_ShouldFail() + { + string actual = "expected actual text"; + string expected = "expected other text"; + Action action = () => throw new CustomException(actual); + + async Task Act() + => await That(action).ThrowsException().WithoutMessageContaining(expected); + + await That(Act).DoesNotThrow(); + } + } + } + } +} diff --git a/Tests/aweXpect.Tests/Exceptions/ThatException.DoesNotHaveMessage.Tests.cs b/Tests/aweXpect.Tests/Exceptions/ThatException.DoesNotHaveMessage.Tests.cs new file mode 100644 index 000000000..2973bf02d --- /dev/null +++ b/Tests/aweXpect.Tests/Exceptions/ThatException.DoesNotHaveMessage.Tests.cs @@ -0,0 +1,115 @@ +namespace aweXpect.Tests; + +public sealed partial class ThatException +{ + public sealed class DoesNotHaveMessage + { + public sealed class Tests + { + [Fact] + public async Task WhenStringsAreEqual_ShouldFail() + { + string actual = "my text"; + Exception subject = new(actual); + + async Task Act() + => await That(subject).DoesNotHaveMessage(actual); + + await That(Act).Throws() + .WithMessage(""" + Expected that subject + has Message not equal to "my text", + but it was "my text" + + Message: + my text + """); + } + + [Fact] + public async Task WhenStringsDiffer_ShouldSucceed() + { + string actual = "actual text"; + string expected = "expected other text"; + Exception subject = new(actual); + + async Task Act() + => await That(subject).DoesNotHaveMessage(expected); + + await That(Act).DoesNotThrow(); + } + + [Fact] + public async Task WhenSubjectIsNull_ShouldFail() + { + Exception? subject = null; + + async Task Act() + => await That(subject).DoesNotHaveMessage("expected text"); + + await That(Act).Throws() + .WithMessage(""" + Expected that subject + has Message not equal to "expected text", + but it was + """); + } + } + + public sealed class NegatedTests + { + [Theory] + [AutoData] + public async Task WhenStringsAreEqual_ShouldSucceed(string actual) + { + Exception subject = new(actual); + + async Task Act() + => await That(subject).DoesNotComplyWith(e => e.DoesNotHaveMessage(actual)); + + await That(Act).DoesNotThrow(); + } + + [Fact] + public async Task WhenStringsDiffer_ShouldFail() + { + string actual = "actual text"; + string expected = "expected other text"; + Exception subject = new(actual); + + async Task Act() + => await That(subject).DoesNotComplyWith(e => e.DoesNotHaveMessage(expected)); + + await That(Act).Throws() + .WithMessage(""" + Expected that subject + has Message equal to "expected other text", + but it was "actual text" which differs at index 0: + ↓ (actual) + "actual text" + "expected other text" + ↑ (expected) + + Message: + actual text + """); + } + + [Fact] + public async Task WhenSubjectIsNull_ShouldFail() + { + Exception? subject = null; + + async Task Act() + => await That(subject).DoesNotComplyWith(e => e.DoesNotHaveMessage("expected text")); + + await That(Act).Throws() + .WithMessage(""" + Expected that subject + has Message equal to "expected text", + but it was + """); + } + } + } +} diff --git a/Tests/aweXpect.Tests/Exceptions/ThatException.DoesNotHaveMessageContaining.Tests.cs b/Tests/aweXpect.Tests/Exceptions/ThatException.DoesNotHaveMessageContaining.Tests.cs new file mode 100644 index 000000000..b9e5266b8 --- /dev/null +++ b/Tests/aweXpect.Tests/Exceptions/ThatException.DoesNotHaveMessageContaining.Tests.cs @@ -0,0 +1,232 @@ +namespace aweXpect.Tests; + +public sealed partial class ThatException +{ + public sealed class DoesNotHaveMessageContaining + { + public sealed class Tests + { + [Fact] + public async Task ShouldCompareCaseSensitive() + { + string message = "FOO"; + MyException exception = new(message); + + async Task Act() + => await That(exception).DoesNotHaveMessageContaining("foo"); + + await That(Act).DoesNotThrow(); + } + + [Fact] + public async Task ShouldIgnorePrecedingText() + { + string message = "some text before foo"; + MyException exception = new(message); + + async Task Act() + => await That(exception).DoesNotHaveMessageContaining("foo"); + + await That(Act).Throws() + .WithMessage(""" + Expected that exception + does not contain Message matching "foo", + but it was "some text before foo" + + Message: + some text before foo + """); + } + + [Fact] + public async Task ShouldIgnoreSucceedingText() + { + string message = "foo and some other text"; + MyException exception = new(message); + + async Task Act() + => await That(exception).DoesNotHaveMessageContaining("foo"); + + await That(Act).Throws() + .WithMessage(""" + Expected that exception + does not contain Message matching "foo", + but it was "foo and some other text" + + Message: + foo and some other text + """); + } + + [Fact] + public async Task WhenStringsAreEqual_ShouldFail() + { + string actual = "my text"; + Exception subject = new(actual); + + async Task Act() + => await That(subject).DoesNotHaveMessageContaining(actual); + + await That(Act).Throws() + .WithMessage(""" + Expected that subject + does not contain Message matching "my text", + but it was "my text" + + Message: + my text + """); + } + + [Fact] + public async Task WhenStringsDiffer_ShouldSucceed() + { + string actual = "actual text"; + string expected = "expected other text"; + Exception subject = new(actual); + + async Task Act() + => await That(subject).DoesNotHaveMessageContaining(expected); + + await That(Act).DoesNotThrow(); + } + } + + public sealed class NegatedTests + { + [Fact] + public async Task CanCompareCaseInsensitive() + { + string message = "_FOO_BAR"; + MyException exception = new(message); + + async Task Act() + => await That(exception) + .DoesNotComplyWith(e => e.DoesNotHaveMessageContaining("foo").IgnoringCase()); + + await That(Act).DoesNotThrow(); + } + + [Fact] + public async Task CanUseWildcardCheck() + { + string message = "_foo-BAR"; + MyException exception = new(message); + + async Task Act() + => await That(exception).DoesNotComplyWith(e => e.DoesNotHaveMessageContaining("f?o-")); + + await That(Act).DoesNotThrow(); + } + + [Fact] + public async Task ShouldCompareCaseSensitive() + { + string message = "FOO"; + MyException exception = new(message); + + async Task Act() + => await That(exception).DoesNotComplyWith(e => e.DoesNotHaveMessageContaining("foo")); + + await That(Act).Throws() + .WithMessage(""" + Expected that exception + contains Message matching "foo", + but it did not match: + ↓ (actual) + "FOO" + "foo" + ↑ (wildcard pattern) + + Message: + FOO + """); + } + + [Fact] + public async Task ShouldIgnorePrecedingText() + { + string message = "some text before foo"; + MyException exception = new(message); + + async Task Act() + => await That(exception).DoesNotComplyWith(e => e.DoesNotHaveMessageContaining("foo")); + + await That(Act).DoesNotThrow(); + } + + [Fact] + public async Task ShouldIgnoreSucceedingText() + { + string message = "foo and some other text"; + MyException exception = new(message); + + async Task Act() + => await That(exception).DoesNotComplyWith(e => e.DoesNotHaveMessageContaining("foo")); + + await That(Act).DoesNotThrow(); + } + + [Fact] + public async Task ShouldIncludeExceptionType() + { + string message = "FOO"; + Exception exception = new CustomException(message); + + async Task Act() + => await That(exception).DoesNotComplyWith(e => e.DoesNotHaveMessageContaining("foo")); + + await That(Act).Throws() + .WithMessage(""" + Expected that exception + contains Message matching "foo", + but it did not match: + ↓ (actual) + "FOO" + "foo" + ↑ (wildcard pattern) + + Message: + FOO + """); + } + + [Fact] + public async Task WhenExpectedIsNull_ShouldSucceed() + { + string message = "foo and some other text"; + MyException exception = new(message); + + async Task Act() + => await That(exception).DoesNotComplyWith(e => e.DoesNotHaveMessageContaining(null)); + + await That(Act).DoesNotThrow(); + } + + [Fact] + public async Task WhenMessagesAreDifferent_ShouldFail() + { + string actual = "expected actual text"; + string expected = "expected other text"; + CustomException subject = new(actual); + + async Task Act() + => await That(subject).DoesNotComplyWith(e => e.DoesNotHaveMessageContaining(expected)); + + await That(Act).Throws() + .WithMessage(""" + Expected that subject + contains Message matching "expected other text", + but it did not match: + ↓ (actual) + "expected actual text" + "expected other text" + ↑ (wildcard pattern) + + Message: + expected actual text + """); + } + } + } +} diff --git a/Tests/aweXpect.Tests/Exceptions/ThatException.HasInner.GenericTests.cs b/Tests/aweXpect.Tests/Exceptions/ThatException.HasInner.GenericTests.cs index 750216037..0114e4965 100644 --- a/Tests/aweXpect.Tests/Exceptions/ThatException.HasInner.GenericTests.cs +++ b/Tests/aweXpect.Tests/Exceptions/ThatException.HasInner.GenericTests.cs @@ -22,6 +22,9 @@ Expected that subject has an inner ThatException.CustomException whose Message is equal to "inner", but it was an Exception: inner + + Message: + inner """); } @@ -141,6 +144,9 @@ await That(Act).Throws() Expected that subject does not have an inner ThatException.CustomException whose Message is equal to "inner", but it had + + Message: + inner """); } diff --git a/Tests/aweXpect.Tests/Exceptions/ThatException.HasInner.TypeTests.cs b/Tests/aweXpect.Tests/Exceptions/ThatException.HasInner.TypeTests.cs index cb72594c5..04a0136ca 100644 --- a/Tests/aweXpect.Tests/Exceptions/ThatException.HasInner.TypeTests.cs +++ b/Tests/aweXpect.Tests/Exceptions/ThatException.HasInner.TypeTests.cs @@ -22,6 +22,9 @@ Expected that subject has an inner ThatException.CustomException whose Message is equal to "inner", but it was an Exception: inner + + Message: + inner """); } @@ -142,6 +145,9 @@ await That(Act).Throws() Expected that subject does not have an inner ThatException.CustomException whose Message is equal to "inner", but it had + + Message: + inner """); } diff --git a/Tests/aweXpect.Tests/Exceptions/ThatException.HasMessage.Tests.cs b/Tests/aweXpect.Tests/Exceptions/ThatException.HasMessage.Tests.cs index 53774701d..060c4c46c 100644 --- a/Tests/aweXpect.Tests/Exceptions/ThatException.HasMessage.Tests.cs +++ b/Tests/aweXpect.Tests/Exceptions/ThatException.HasMessage.Tests.cs @@ -76,6 +76,9 @@ await That(Act).Throws() Expected that subject has Message not equal to "my text", but it was "my text" + + Message: + my text """); } diff --git a/Tests/aweXpect.Tests/Exceptions/ThatException.HasMessageContaining.Tests.cs b/Tests/aweXpect.Tests/Exceptions/ThatException.HasMessageContaining.Tests.cs index 521132949..6d8a9fcfa 100644 --- a/Tests/aweXpect.Tests/Exceptions/ThatException.HasMessageContaining.Tests.cs +++ b/Tests/aweXpect.Tests/Exceptions/ThatException.HasMessageContaining.Tests.cs @@ -156,6 +156,9 @@ await That(Act).Throws() Expected that subject does not contain Message matching "my text", but it was "my text" + + Message: + my text """); } From 9d7a849b905f8a83e24ef1a205b0dc05c224d376 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Breu=C3=9F=20Valentin?= Date: Fri, 19 Sep 2025 16:07:53 +0200 Subject: [PATCH 2/2] Accept API changes --- .../That/Exceptions/ThatException.HasMessageContaining.cs | 2 +- Tests/aweXpect.Api.Tests/Expected/aweXpect_net8.0.txt | 6 ++++++ .../aweXpect.Api.Tests/Expected/aweXpect_netstandard2.0.txt | 6 ++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Source/aweXpect/That/Exceptions/ThatException.HasMessageContaining.cs b/Source/aweXpect/That/Exceptions/ThatException.HasMessageContaining.cs index ea7738585..c4cc091e2 100644 --- a/Source/aweXpect/That/Exceptions/ThatException.HasMessageContaining.cs +++ b/Source/aweXpect/That/Exceptions/ThatException.HasMessageContaining.cs @@ -84,7 +84,7 @@ protected override void AppendNormalExpectation(StringBuilder stringBuilder, str { stringBuilder.Append("contains Message matching "); } - + options.Exactly(); stringBuilder.Append(options.GetExpectation(expected, equalityGrammars)["equal to ".Length..]); options.AsWildcard(); diff --git a/Tests/aweXpect.Api.Tests/Expected/aweXpect_net8.0.txt b/Tests/aweXpect.Api.Tests/Expected/aweXpect_net8.0.txt index 6ad52bf04..e4a5ea6a9 100644 --- a/Tests/aweXpect.Api.Tests/Expected/aweXpect_net8.0.txt +++ b/Tests/aweXpect.Api.Tests/Expected/aweXpect_net8.0.txt @@ -307,6 +307,10 @@ namespace aweXpect where TException : System.ArgumentException? { } public static aweXpect.Results.AndOrResult> WithRecursiveInnerExceptions(this aweXpect.Delegates.ThatDelegateThrows source, System.Action>> expectations) where TException : System.Exception? { } + public static aweXpect.Results.StringEqualityTypeResult> WithoutMessage(this aweXpect.Delegates.ThatDelegateThrows source, string unexpected) + where TException : System.Exception? { } + public static aweXpect.Results.StringEqualityResult> WithoutMessageContaining(this aweXpect.Delegates.ThatDelegateThrows source, string? unexpected) + where TException : System.Exception? { } } public static class ThatDictionary { @@ -706,6 +710,8 @@ namespace aweXpect } public static class ThatException { + public static aweXpect.Results.StringEqualityTypeResult> DoesNotHaveMessage(this aweXpect.Core.IThat source, string unexpected) { } + public static aweXpect.Results.StringEqualityTypeResult> DoesNotHaveMessageContaining(this aweXpect.Core.IThat source, string? unexpected) { } public static aweXpect.Results.AndOrResult> HasHResult(this aweXpect.Core.IThat source, int expected) where TException : System.Exception? { } public static aweXpect.Results.AndOrResult> HasInner(this aweXpect.Core.IThat source, System.Type innerExceptionType) { } diff --git a/Tests/aweXpect.Api.Tests/Expected/aweXpect_netstandard2.0.txt b/Tests/aweXpect.Api.Tests/Expected/aweXpect_netstandard2.0.txt index c4b1faaf1..77371666e 100644 --- a/Tests/aweXpect.Api.Tests/Expected/aweXpect_netstandard2.0.txt +++ b/Tests/aweXpect.Api.Tests/Expected/aweXpect_netstandard2.0.txt @@ -141,6 +141,10 @@ namespace aweXpect where TException : System.ArgumentException? { } public static aweXpect.Results.AndOrResult> WithRecursiveInnerExceptions(this aweXpect.Delegates.ThatDelegateThrows source, System.Action>> expectations) where TException : System.Exception? { } + public static aweXpect.Results.StringEqualityTypeResult> WithoutMessage(this aweXpect.Delegates.ThatDelegateThrows source, string unexpected) + where TException : System.Exception? { } + public static aweXpect.Results.StringEqualityResult> WithoutMessageContaining(this aweXpect.Delegates.ThatDelegateThrows source, string? unexpected) + where TException : System.Exception? { } } public static class ThatDictionary { @@ -469,6 +473,8 @@ namespace aweXpect } public static class ThatException { + public static aweXpect.Results.StringEqualityTypeResult> DoesNotHaveMessage(this aweXpect.Core.IThat source, string unexpected) { } + public static aweXpect.Results.StringEqualityTypeResult> DoesNotHaveMessageContaining(this aweXpect.Core.IThat source, string? unexpected) { } public static aweXpect.Results.AndOrResult> HasHResult(this aweXpect.Core.IThat source, int expected) where TException : System.Exception? { } public static aweXpect.Results.AndOrResult> HasInner(this aweXpect.Core.IThat source, System.Type innerExceptionType) { }