diff --git a/Pipeline/Build.cs b/Pipeline/Build.cs index a07a0cc08..25356ef59 100644 --- a/Pipeline/Build.cs +++ b/Pipeline/Build.cs @@ -19,7 +19,7 @@ partial class Build : NukeBuild /// /// Afterward, you can update the package reference in `Directory.Packages.props` and reset this flag. /// - readonly BuildScope BuildScope = BuildScope.Default; + readonly BuildScope BuildScope = BuildScope.CoreOnly; [Parameter("Github Token")] readonly string GithubToken; diff --git a/Source/aweXpect.Core/Delegates/ThatDelegate.Throws.cs b/Source/aweXpect.Core/Delegates/ThatDelegate.Throws.cs index f4027ee00..192eddc20 100644 --- a/Source/aweXpect.Core/Delegates/ThatDelegate.Throws.cs +++ b/Source/aweXpect.Core/Delegates/ThatDelegate.Throws.cs @@ -18,7 +18,7 @@ public ThatDelegateThrows Throws() { ThrowsOption throwOptions = new(); return new ThatDelegateThrows(ExpectationBuilder - .AddConstraint((it, grammars) => new DelegateIsNotNullConstraint(it, grammars)) + .AddConstraint((it, grammars) => new DelegateIsNotNullWithinTimeoutConstraint(it, grammars, throwOptions)) .ForWhich(d => d.Exception) .AddConstraint((it, grammars) => new ThrowsConstraint(it, grammars, typeof(TException), throwOptions)) .And(" "), @@ -32,7 +32,7 @@ public ThatDelegateThrows Throws(Type exceptionType) { ThrowsOption throwOptions = new(); return new ThatDelegateThrows(ExpectationBuilder - .AddConstraint((it, grammars) => new DelegateIsNotNullConstraint(it, grammars)) + .AddConstraint((it, grammars) => new DelegateIsNotNullWithinTimeoutConstraint(it, grammars, throwOptions)) .ForWhich(d => d.Exception) .AddConstraint((it, grammars) => new ThrowsConstraint(it, grammars, exceptionType, throwOptions)) .And(" "), @@ -89,6 +89,12 @@ public override void AppendExpectation(StringBuilder stringBuilder, string? inde { stringBuilder.Append("throws ").Append(Formatter.Format(exceptionType).PrependAOrAn()); } + + if (throwOptions.ExecutionTimeOptions is not null) + { + stringBuilder.Append(' '); + throwOptions.ExecutionTimeOptions.AppendTo(stringBuilder, "in "); + } } public override void AppendResult(StringBuilder stringBuilder, string? indentation = null) diff --git a/Source/aweXpect.Core/Delegates/ThatDelegate.ThrowsExactly.cs b/Source/aweXpect.Core/Delegates/ThatDelegate.ThrowsExactly.cs index 45eab29e6..b8a6686de 100644 --- a/Source/aweXpect.Core/Delegates/ThatDelegate.ThrowsExactly.cs +++ b/Source/aweXpect.Core/Delegates/ThatDelegate.ThrowsExactly.cs @@ -18,7 +18,7 @@ public ThatDelegateThrows ThrowsExactly() { ThrowsOption throwOptions = new(); return new ThatDelegateThrows(ExpectationBuilder - .AddConstraint((it, grammars) => new DelegateIsNotNullConstraint(it, grammars)) + .AddConstraint((it, grammars) => new DelegateIsNotNullWithinTimeoutConstraint(it, grammars, throwOptions)) .ForWhich(d => d.Exception) .AddConstraint((it, grammars) => new ThrowsExactlyConstraint(it, grammars, typeof(TException), throwOptions)) @@ -33,7 +33,7 @@ public ThatDelegateThrows ThrowsExactly(Type exceptionType) { ThrowsOption throwOptions = new(); return new ThatDelegateThrows(ExpectationBuilder - .AddConstraint((it, grammars) => new DelegateIsNotNullConstraint(it, grammars)) + .AddConstraint((it, grammars) => new DelegateIsNotNullWithinTimeoutConstraint(it, grammars, throwOptions)) .ForWhich(d => d.Exception) .AddConstraint((it, grammars) => new ThrowsExactlyConstraint(it, grammars, exceptionType, throwOptions)) .And(" "), @@ -86,6 +86,12 @@ public override void AppendExpectation(StringBuilder stringBuilder, string? inde { stringBuilder.Append("throws exactly ").Append(Formatter.Format(exceptionType).PrependAOrAn()); } + + if (throwOptions.ExecutionTimeOptions is not null) + { + stringBuilder.Append(' '); + throwOptions.ExecutionTimeOptions.AppendTo(stringBuilder, "in "); + } } public override void AppendResult(StringBuilder stringBuilder, string? indentation = null) diff --git a/Source/aweXpect.Core/Delegates/ThatDelegate.ThrowsException.cs b/Source/aweXpect.Core/Delegates/ThatDelegate.ThrowsException.cs index b6068e7cd..eefc497f3 100644 --- a/Source/aweXpect.Core/Delegates/ThatDelegate.ThrowsException.cs +++ b/Source/aweXpect.Core/Delegates/ThatDelegate.ThrowsException.cs @@ -12,7 +12,7 @@ public ThatDelegateThrows ThrowsException() { ThrowsOption throwOptions = new(); return new ThatDelegateThrows(ExpectationBuilder - .AddConstraint((it, grammars) => new DelegateIsNotNullConstraint(it, grammars)) + .AddConstraint((it, grammars) => new DelegateIsNotNullWithinTimeoutConstraint(it, grammars, throwOptions)) .ForWhich(d => d.Exception) .AddConstraint((it, grammars) => new ThrowsConstraint(it, grammars, typeof(Exception), throwOptions)) .And(" "), diff --git a/Source/aweXpect.Core/Delegates/ThatDelegate.WithValue.ExecutesIn.cs b/Source/aweXpect.Core/Delegates/ThatDelegate.WithValue.ExecutesIn.cs index 80e6dcb22..9e278d187 100644 --- a/Source/aweXpect.Core/Delegates/ThatDelegate.WithValue.ExecutesIn.cs +++ b/Source/aweXpect.Core/Delegates/ThatDelegate.WithValue.ExecutesIn.cs @@ -51,7 +51,10 @@ public ConstraintResult IsMetBy(DelegateValue value) } public override void AppendExpectation(StringBuilder stringBuilder, string? indentation = null) - => stringBuilder.Append("executes in ").Append(options); + { + stringBuilder.Append("executes "); + options.AppendTo(stringBuilder, "in "); + } public override void AppendResult(StringBuilder stringBuilder, string? indentation = null) { diff --git a/Source/aweXpect.Core/Delegates/ThatDelegate.WithoutValue.ExecutesIn.cs b/Source/aweXpect.Core/Delegates/ThatDelegate.WithoutValue.ExecutesIn.cs index 6cd9a048e..91d412745 100644 --- a/Source/aweXpect.Core/Delegates/ThatDelegate.WithoutValue.ExecutesIn.cs +++ b/Source/aweXpect.Core/Delegates/ThatDelegate.WithoutValue.ExecutesIn.cs @@ -51,7 +51,10 @@ public ConstraintResult IsMetBy(DelegateValue value) } public override void AppendExpectation(StringBuilder stringBuilder, string? indentation = null) - => stringBuilder.Append("executes in ").Append(options); + { + stringBuilder.Append("executes "); + options.AppendTo(stringBuilder, "in "); + } public override void AppendResult(StringBuilder stringBuilder, string? indentation = null) { diff --git a/Source/aweXpect.Core/Delegates/ThatDelegate.cs b/Source/aweXpect.Core/Delegates/ThatDelegate.cs index d6d08f76d..a71df19c6 100644 --- a/Source/aweXpect.Core/Delegates/ThatDelegate.cs +++ b/Source/aweXpect.Core/Delegates/ThatDelegate.cs @@ -5,6 +5,7 @@ using aweXpect.Core.Constraints; using aweXpect.Core.Helpers; using aweXpect.Core.Sources; +using aweXpect.Options; namespace aweXpect.Delegates; @@ -34,13 +35,28 @@ internal static string FormatForMessage(Exception? exception) return message; } - private sealed class DelegateIsNotNullConstraint(string it, ExpectationGrammars grammars) + private sealed class DelegateIsNotNullWithinTimeoutConstraint(string it, ExpectationGrammars grammars, ThrowsOption options) : ConstraintResult(grammars), IValueConstraint { + private DelegateValue? _actual; public ConstraintResult IsMetBy(DelegateValue value) { - Outcome = value.IsNull ? Outcome.Failure : Outcome.Success; + _actual = value; + if (value.IsNull) + { + Outcome = Outcome.Failure; + return this; + } + + if (options.ExecutionTimeOptions is not null && + !options.ExecutionTimeOptions.IsWithinLimit(value.Duration)) + { + Outcome = Outcome.Failure; + return this; + } + + Outcome = Outcome.Success; return this; } @@ -50,7 +66,17 @@ public override void AppendExpectation(StringBuilder stringBuilder, string? inde } public override void AppendResult(StringBuilder stringBuilder, string? indentation = null) - => stringBuilder.ItWasNull(it); + { + if (_actual?.IsNull != false) + { + stringBuilder.ItWasNull(it); + } + else if (options.ExecutionTimeOptions is not null) + { + stringBuilder.Append(it).Append(" took "); + options.ExecutionTimeOptions.AppendFailureResult(stringBuilder, _actual.Duration); + } + } public override bool TryGetValue([NotNullWhen(true)] out TValue? value) where TValue : default { @@ -65,6 +91,8 @@ public override ConstraintResult Negate() internal class ThrowsOption { public bool DoCheckThrow { get; private set; } = true; + + public TimeSpanEqualityOptions? ExecutionTimeOptions { get; set; } public void CheckThrow(bool doCheckThrow) => DoCheckThrow = doCheckThrow; } diff --git a/Source/aweXpect.Core/Delegates/ThatDelegateThrows.Which.cs b/Source/aweXpect.Core/Delegates/ThatDelegateThrows.Which.cs index ca87b5641..c27576d60 100644 --- a/Source/aweXpect.Core/Delegates/ThatDelegateThrows.Which.cs +++ b/Source/aweXpect.Core/Delegates/ThatDelegateThrows.Which.cs @@ -1,18 +1,12 @@ -using System; -using System.Linq.Expressions; -using aweXpect.Core; -using aweXpect.Core.Sources; -using aweXpect.Results; +using aweXpect.Core; namespace aweXpect.Delegates; public partial class ThatDelegateThrows { - /// /// Further expectations on the /// public IThat Which => new ThatSubject(ExpectationBuilder.And(" which ")); - } diff --git a/Source/aweXpect.Core/Delegates/ThatDelegateThrows.Whose.cs b/Source/aweXpect.Core/Delegates/ThatDelegateThrows.Whose.cs index 43f6c2c7f..ea3316d9e 100644 --- a/Source/aweXpect.Core/Delegates/ThatDelegateThrows.Whose.cs +++ b/Source/aweXpect.Core/Delegates/ThatDelegateThrows.Whose.cs @@ -13,9 +13,11 @@ public partial class ThatDelegateThrows public AndOrResult> Whose( Func memberSelector, Action> expectations, - [CallerArgumentExpression("memberSelector")] string doNotPopulateThisValue = "") + [CallerArgumentExpression("memberSelector")] + string doNotPopulateThisValue = "") => new(ExpectationBuilder.ForMember( - MemberAccessor.FromFuncAsMemberAccessor(memberSelector, doNotPopulateThisValue), + MemberAccessor.FromFuncAsMemberAccessor(memberSelector, + doNotPopulateThisValue), (member, expectation) => expectation.Append("whose ").Append(member)) .AddExpectations(e => expectations(new ThatSubject(e))), this); diff --git a/Source/aweXpect.Core/Delegates/ThatDelegateThrows.Within.cs b/Source/aweXpect.Core/Delegates/ThatDelegateThrows.Within.cs new file mode 100644 index 000000000..d5da78224 --- /dev/null +++ b/Source/aweXpect.Core/Delegates/ThatDelegateThrows.Within.cs @@ -0,0 +1,18 @@ +using System; +using aweXpect.Options; + +namespace aweXpect.Delegates; + +public partial class ThatDelegateThrows +{ + /// + /// Verifies that the delegate throws within the given . + /// + public ThatDelegateThrows Within(TimeSpan duration) + { + TimeSpanEqualityOptions options = new(); + options.Within(duration); + _throwOptions.ExecutionTimeOptions = options; + return this; + } +} diff --git a/Source/aweXpect.Core/Options/TimeSpanEqualityOptions.cs b/Source/aweXpect.Core/Options/TimeSpanEqualityOptions.cs index bde075752..e4a4a27ce 100644 --- a/Source/aweXpect.Core/Options/TimeSpanEqualityOptions.cs +++ b/Source/aweXpect.Core/Options/TimeSpanEqualityOptions.cs @@ -32,8 +32,17 @@ public bool IsWithinLimit(TimeSpan? actual) public void AppendFailureResult(StringBuilder stringBuilder, TimeSpan actual) => _limit?.AppendFailureResult(stringBuilder, actual); - /// - public override string ToString() => _limit?.ToString() ?? ""; + /// + /// Appends the and the option description to the . + /// + public void AppendTo(StringBuilder stringBuilder, string prefix) + => _limit?.AppendTo(stringBuilder, prefix); + + /// + /// Verifies that the value is within the given . + /// + public void Within(TimeSpan duration) + => _limit = new MaximumLimit(duration, true); /// /// Verifies that the value is at most . @@ -74,6 +83,8 @@ private abstract record Limit public virtual void AppendFailureResult(StringBuilder stringBuilder, TimeSpan actual) => Formatter.Format(stringBuilder, actual); + + public abstract void AppendTo(StringBuilder stringBuilder, string prefix); } private sealed record ApproximatelyLimit(TimeSpan Expected, TimeSpan Tolerance) : Limit @@ -81,8 +92,14 @@ private sealed record ApproximatelyLimit(TimeSpan Expected, TimeSpan Tolerance) public override bool IsWithinLimit(TimeSpan actual) => actual >= Expected - Tolerance && actual <= Expected + Tolerance; - public override string ToString() - => $"approximately {Formatter.Format(Expected)} ± {Formatter.Format(Tolerance)}"; + public override void AppendTo(StringBuilder stringBuilder, string prefix) + { + stringBuilder.Append(prefix); + stringBuilder.Append("approximately "); + Formatter.Format(stringBuilder, Expected); + stringBuilder.Append(" ± "); + Formatter.Format(stringBuilder, Tolerance); + } public override void AppendFailureResult(StringBuilder stringBuilder, TimeSpan actual) { @@ -100,7 +117,14 @@ private sealed record BetweenLimit(TimeSpan Minimum, TimeSpan Maximum) : Limit public override bool IsWithinLimit(TimeSpan actual) => actual >= Minimum && actual <= Maximum; - public override string ToString() => $"between {Formatter.Format(Minimum)} and {Formatter.Format(Maximum)}"; + public override void AppendTo(StringBuilder stringBuilder, string prefix) + { + stringBuilder.Append(prefix); + stringBuilder.Append("between "); + Formatter.Format(stringBuilder, Minimum); + stringBuilder.Append(" and "); + Formatter.Format(stringBuilder, Maximum); + } public override void AppendFailureResult(StringBuilder stringBuilder, TimeSpan actual) { @@ -118,7 +142,12 @@ private sealed record MinimumLimit(TimeSpan Minimum) : Limit public override bool IsWithinLimit(TimeSpan actual) => actual >= Minimum; - public override string ToString() => $"at least {Formatter.Format(Minimum)}"; + public override void AppendTo(StringBuilder stringBuilder, string prefix) + { + stringBuilder.Append(prefix); + stringBuilder.Append("at least "); + Formatter.Format(stringBuilder, Minimum); + } public override void AppendFailureResult(StringBuilder stringBuilder, TimeSpan actual) { @@ -127,11 +156,24 @@ public override void AppendFailureResult(StringBuilder stringBuilder, TimeSpan a } } - private sealed record MaximumLimit(TimeSpan Maximum) : Limit + private sealed record MaximumLimit(TimeSpan Maximum, bool IsWithin = false) : Limit { public override bool IsWithinLimit(TimeSpan actual) => actual <= Maximum; - public override string ToString() => $"at most {Formatter.Format(Maximum)}"; + public override void AppendTo(StringBuilder stringBuilder, string prefix) + { + if (IsWithin) + { + stringBuilder.Append("within "); + Formatter.Format(stringBuilder, Maximum); + } + else + { + stringBuilder.Append(prefix); + stringBuilder.Append("at most "); + Formatter.Format(stringBuilder, Maximum); + } + } } } diff --git a/Tests/aweXpect.Core.Api.Tests/Expected/aweXpect.Core_net8.0.txt b/Tests/aweXpect.Core.Api.Tests/Expected/aweXpect.Core_net8.0.txt index b673fdc14..a067b0bea 100644 --- a/Tests/aweXpect.Core.Api.Tests/Expected/aweXpect.Core_net8.0.txt +++ b/Tests/aweXpect.Core.Api.Tests/Expected/aweXpect.Core_net8.0.txt @@ -500,6 +500,7 @@ namespace aweXpect.Delegates where TInnerException : System.Exception { } public aweXpect.Results.AndOrResult> WithInnerException() { } public aweXpect.Results.AndOrResult> WithInnerException(System.Action> expectations) { } + public aweXpect.Delegates.ThatDelegateThrows Within(System.TimeSpan duration) { } } } namespace aweXpect.Equivalency @@ -846,12 +847,13 @@ namespace aweXpect.Options { public TimeSpanEqualityOptions() { } public void AppendFailureResult(System.Text.StringBuilder stringBuilder, System.TimeSpan actual) { } + public void AppendTo(System.Text.StringBuilder stringBuilder, string prefix) { } public void Approximately(System.TimeSpan expected, System.TimeSpan tolerance) { } public void AtLeast(System.TimeSpan minimum) { } public void AtMost(System.TimeSpan maximum) { } public void Between(System.TimeSpan minimum, System.TimeSpan maximum) { } public bool IsWithinLimit(System.TimeSpan? actual) { } - public override string ToString() { } + public void Within(System.TimeSpan duration) { } } public class TimeTolerance { diff --git a/Tests/aweXpect.Core.Api.Tests/Expected/aweXpect.Core_netstandard2.0.txt b/Tests/aweXpect.Core.Api.Tests/Expected/aweXpect.Core_netstandard2.0.txt index 0fa939b7b..99f7b6ddf 100644 --- a/Tests/aweXpect.Core.Api.Tests/Expected/aweXpect.Core_netstandard2.0.txt +++ b/Tests/aweXpect.Core.Api.Tests/Expected/aweXpect.Core_netstandard2.0.txt @@ -500,6 +500,7 @@ namespace aweXpect.Delegates where TInnerException : System.Exception { } public aweXpect.Results.AndOrResult> WithInnerException() { } public aweXpect.Results.AndOrResult> WithInnerException(System.Action> expectations) { } + public aweXpect.Delegates.ThatDelegateThrows Within(System.TimeSpan duration) { } } } namespace aweXpect.Equivalency @@ -829,12 +830,13 @@ namespace aweXpect.Options { public TimeSpanEqualityOptions() { } public void AppendFailureResult(System.Text.StringBuilder stringBuilder, System.TimeSpan actual) { } + public void AppendTo(System.Text.StringBuilder stringBuilder, string prefix) { } public void Approximately(System.TimeSpan expected, System.TimeSpan tolerance) { } public void AtLeast(System.TimeSpan minimum) { } public void AtMost(System.TimeSpan maximum) { } public void Between(System.TimeSpan minimum, System.TimeSpan maximum) { } public bool IsWithinLimit(System.TimeSpan? actual) { } - public override string ToString() { } + public void Within(System.TimeSpan duration) { } } public class TimeTolerance { diff --git a/Tests/aweXpect.Tests/Delegates/ThatDelegate.Throws.Within.Tests.cs b/Tests/aweXpect.Tests/Delegates/ThatDelegate.Throws.Within.Tests.cs new file mode 100644 index 000000000..90a7dc899 --- /dev/null +++ b/Tests/aweXpect.Tests/Delegates/ThatDelegate.Throws.Within.Tests.cs @@ -0,0 +1,349 @@ +namespace aweXpect.Tests; + +public sealed partial class ThatDelegate +{ + public sealed partial class Throws + { + public sealed class Within + { + public sealed class GenericTests + { + [Fact] + public async Task ShouldSupportChainedConstraints() + { + Action action = () => { }; + + async Task Act() + => await That(action).Throws().Within(5.Seconds()).WithMessage("foo"); + + await That(Act).ThrowsException() + .WithMessage(""" + Expected that action + throws an ArgumentException within 0:05 with Message equal to "foo", + but it did not throw any exception + """); + } + + [Theory] + [AutoData] + public async Task WhenAwaited_ShouldReturnThrownException(string value) + { + Exception exception = new CustomException + { + Value = value + }; + Action action = () => throw exception; + + CustomException result = + await That(action).Throws().Within(5.Seconds()); + + await That(result.Value).IsEqualTo(value); + await That(result).IsSameAs(exception); + } + + [Fact] + public async Task WhenExactExceptionTypeIsThrownInTime_ShouldSucceed() + { + Exception exception = new CustomException(); + Action action = () => throw exception; + + async Task Act() + => await That(action).Throws().Within(5.Seconds()); + + await That(Act).DoesNotThrow(); + } + + [Fact] + public async Task WhenExactExceptionTypeIsThrownTooLate_ShouldFail() + { + Exception exception = new CustomException(); + Action action = () => + { + Task.Delay(50.Milliseconds()).Wait(); + throw exception; + }; + + async Task Act() + => await That(action).Throws().Within(5.Milliseconds()); + + await That(Act).ThrowsException() + .WithMessage(""" + Expected that action + throws a ThatDelegate.CustomException within 0:00.005, + but it took * + """).AsWildcard(); + } + + [Fact] + public async Task WhenNoExceptionIsThrownAndExecutionTimeIsTooLarge_ShouldFail() + { + Action action = () => + { + Task.Delay(50.Milliseconds()).Wait(); + }; + + async Task Act() + => await That(action).Throws().Within(5.Milliseconds()); + + await That(Act).ThrowsException() + .WithMessage(""" + Expected that action + throws an exception within 0:00.005, + but it took * + """).AsWildcard(); + } + + [Fact] + public async Task WhenNoExceptionIsThrownInTime_ShouldFail() + { + Action action = () => { }; + + async Task Act() + => await That(action).Throws().Within(5.Seconds()); + + await That(Act).ThrowsException() + .WithMessage(""" + Expected that action + throws an exception within 0:05, + but it did not throw any exception + """); + } + + [Theory] + [AutoData] + public async Task WhenOtherExceptionIsThrown_ShouldFail(string message) + { + Exception exception = new OtherException(message); + Action action = () => throw exception; + + async Task Act() + => await That(action).Throws().Within(5.Seconds()); + + await That(Act).ThrowsException() + .WithMessage($""" + Expected that action + throws a ThatDelegate.CustomException within 0:05, + but it did throw a ThatDelegate.OtherException: + {message} + """); + } + + [Fact] + public async Task WhenSubCustomExceptionIsThrown_ShouldSucceed() + { + Exception exception = new SubCustomException(); + Action action = () => throw exception; + + async Task Act() + => await That(action).Throws().Within(5.Seconds()); + + await That(Act).DoesNotThrow(); + } + + [Fact] + public async Task WhenSubjectIsNull_ShouldFail() + { + Action? subject = null; + + async Task Act() + => await That(subject!).Throws().Within(5.Seconds()); + + await That(Act).Throws() + .WithMessage(""" + Expected that subject + throws a ThatDelegate.CustomException within 0:05, + but it was + """); + } + + [Theory] + [AutoData] + public async Task WhenSupertypeExceptionIsThrown_ShouldFail(string message) + { + Exception exception = new CustomException(message); + Action action = () => throw exception; + + async Task Act() + => await That(action).Throws().Within(6.Seconds()); + + await That(Act).ThrowsException() + .WithMessage($""" + Expected that action + throws a ThatDelegate.SubCustomException within 0:06, + but it did throw a ThatDelegate.CustomException: + {message} + """); + } + } + + public sealed class TypeTests + { + [Fact] + public async Task ShouldSupportChainedConstraints() + { + Action action = () => { }; + + async Task Act() + => await That(action).Throws(typeof(ArgumentException)).Within(5.Seconds()).WithMessage("foo"); + + await That(Act).ThrowsException() + .WithMessage(""" + Expected that action + throws an ArgumentException within 0:05 with Message equal to "foo", + but it did not throw any exception + """); + } + + [Theory] + [AutoData] + public async Task WhenAwaited_ShouldReturnThrownException(string value) + { + Exception exception = new CustomException + { + Value = value + }; + Action action = () => throw exception; + + Exception result = + await That(action).Throws(typeof(CustomException)).Within(5.Seconds()); + + await That(result).IsSameAs(exception); + } + + [Fact] + public async Task WhenExactExceptionTypeIsThrownInTime_ShouldSucceed() + { + Exception exception = new CustomException(); + Action action = () => throw exception; + + async Task Act() + => await That(action).Throws(typeof(CustomException)).Within(5.Seconds()); + + await That(Act).DoesNotThrow(); + } + + [Fact] + public async Task WhenExactExceptionTypeIsThrownTooLate_ShouldFail() + { + Exception exception = new CustomException(); + Action action = () => + { + Task.Delay(50.Milliseconds()).Wait(); + throw exception; + }; + + async Task Act() + => await That(action).Throws(typeof(CustomException)).Within(5.Milliseconds()); + + await That(Act).ThrowsException() + .WithMessage(""" + Expected that action + throws a ThatDelegate.CustomException within 0:00.005, + but it took * + """).AsWildcard(); + } + + [Fact] + public async Task WhenNoExceptionIsThrownAndExecutionTimeIsTooLarge_ShouldFail() + { + Action action = () => + { + Task.Delay(50.Milliseconds()).Wait(); + }; + + async Task Act() + => await That(action).Throws(typeof(Exception)).Within(5.Milliseconds()); + + await That(Act).ThrowsException() + .WithMessage(""" + Expected that action + throws an exception within 0:00.005, + but it took * + """).AsWildcard(); + } + + [Fact] + public async Task WhenNoExceptionIsThrownInTime_ShouldFail() + { + Action action = () => { }; + + async Task Act() + => await That(action).Throws(typeof(Exception)).Within(5.Seconds()).Because("it should"); + + await That(Act).ThrowsException() + .WithMessage(""" + Expected that action + throws an exception within 0:05, because it should, + but it did not throw any exception + """); + } + + [Theory] + [AutoData] + public async Task WhenOtherExceptionIsThrown_ShouldFail(string message) + { + Exception exception = new OtherException(message); + Action action = () => throw exception; + + async Task Act() + => await That(action).Throws(typeof(CustomException)).Within(5.Seconds()); + + await That(Act).ThrowsException() + .WithMessage($""" + Expected that action + throws a ThatDelegate.CustomException within 0:05, + but it did throw a ThatDelegate.OtherException: + {message} + """); + } + + [Fact] + public async Task WhenSubCustomExceptionIsThrown_ShouldSucceed() + { + Exception exception = new SubCustomException(); + Action action = () => throw exception; + + async Task Act() + => await That(action).Throws(typeof(CustomException)).Within(5.Seconds()); + + await That(Act).DoesNotThrow(); + } + + [Fact] + public async Task WhenSubjectIsNull_ShouldFail() + { + Action? subject = null; + + async Task Act() + => await That(subject!).Throws(typeof(CustomException)).Within(5.Seconds()); + + await That(Act).Throws() + .WithMessage(""" + Expected that subject + throws a ThatDelegate.CustomException within 0:05, + but it was + """); + } + + [Theory] + [AutoData] + public async Task WhenSupertypeExceptionIsThrown_ShouldFail(string message) + { + Exception exception = new CustomException(message); + Action action = () => throw exception; + + async Task Act() + => await That(action).Throws(typeof(SubCustomException)).Within(6.Seconds()); + + await That(Act).ThrowsException() + .WithMessage($""" + Expected that action + throws a ThatDelegate.SubCustomException within 0:06, + but it did throw a ThatDelegate.CustomException: + {message} + """); + } + } + } + } +} diff --git a/Tests/aweXpect.Tests/Delegates/ThatDelegate.ThrowsExactly.Within.Tests.cs b/Tests/aweXpect.Tests/Delegates/ThatDelegate.ThrowsExactly.Within.Tests.cs new file mode 100644 index 000000000..78edbfce9 --- /dev/null +++ b/Tests/aweXpect.Tests/Delegates/ThatDelegate.ThrowsExactly.Within.Tests.cs @@ -0,0 +1,362 @@ +namespace aweXpect.Tests; + +public sealed partial class ThatDelegate +{ + public sealed partial class ThrowsExactly + { + public sealed class Within + { + public sealed class GenericTests + { + [Fact] + public async Task ShouldSupportChainedConstraints() + { + Action action = () => { }; + + async Task Act() + => await That(action).ThrowsExactly().Within(5.Seconds()).WithMessage("foo"); + + await That(Act).ThrowsException() + .WithMessage(""" + Expected that action + throws exactly an ArgumentException within 0:05 with Message equal to "foo", + but it did not throw any exception + """); + } + + [Theory] + [AutoData] + public async Task WhenAwaited_ShouldReturnThrownException(string value) + { + Exception exception = new CustomException + { + Value = value + }; + Action action = () => throw exception; + + CustomException result = + await That(action).ThrowsExactly().Within(5.Seconds()); + + await That(result.Value).IsEqualTo(value); + await That(result).IsSameAs(exception); + } + + [Fact] + public async Task WhenExactExceptionTypeIsThrownInTime_ShouldSucceed() + { + Exception exception = new CustomException(); + Action action = () => throw exception; + + async Task Act() + => await That(action).ThrowsExactly().Within(5.Seconds()); + + await That(Act).DoesNotThrow(); + } + + [Fact] + public async Task WhenExactExceptionTypeIsThrownTooLate_ShouldFail() + { + Exception exception = new CustomException(); + Action action = () => + { + Task.Delay(50.Milliseconds()).Wait(); + throw exception; + }; + + async Task Act() + => await That(action).ThrowsExactly().Within(5.Milliseconds()); + + await That(Act).ThrowsException() + .WithMessage(""" + Expected that action + throws exactly a ThatDelegate.CustomException within 0:00.005, + but it took * + """).AsWildcard(); + } + + [Fact] + public async Task WhenNoExceptionIsThrownAndExecutionTimeIsTooLarge_ShouldFail() + { + Action action = () => + { + Task.Delay(50.Milliseconds()).Wait(); + }; + + async Task Act() + => await That(action).ThrowsExactly().Within(5.Milliseconds()); + + await That(Act).ThrowsException() + .WithMessage(""" + Expected that action + throws exactly an Exception within 0:00.005, + but it took * + """).AsWildcard(); + } + + [Fact] + public async Task WhenNoExceptionIsThrownInTime_ShouldFail() + { + Action action = () => { }; + + async Task Act() + => await That(action).ThrowsExactly().Within(5.Seconds()); + + await That(Act).ThrowsException() + .WithMessage(""" + Expected that action + throws exactly an Exception within 0:05, + but it did not throw any exception + """); + } + + [Theory] + [AutoData] + public async Task WhenOtherExceptionIsThrown_ShouldFail(string message) + { + Exception exception = new OtherException(message); + Action action = () => throw exception; + + async Task Act() + => await That(action).ThrowsExactly().Within(5.Seconds()); + + await That(Act).ThrowsException() + .WithMessage($""" + Expected that action + throws exactly a ThatDelegate.CustomException within 0:05, + but it did throw a ThatDelegate.OtherException: + {message} + """); + } + + [Fact] + public async Task WhenSubCustomExceptionIsThrown_ShouldFail() + { + Exception exception = new SubCustomException(); + Action action = () => throw exception; + + async Task Act() + => await That(action).ThrowsExactly().Within(5.Seconds()); + + await That(Act).ThrowsException() + .WithMessage(""" + Expected that action + throws exactly a ThatDelegate.CustomException within 0:05, + but it did throw a ThatDelegate.SubCustomException: + WhenSubCustomExceptionIsThrown_ShouldFail + """); + } + + [Fact] + public async Task WhenSubjectIsNull_ShouldFail() + { + Action? subject = null; + + async Task Act() + => await That(subject!).ThrowsExactly().Within(5.Seconds()); + + await That(Act).ThrowsExactly() + .WithMessage(""" + Expected that subject + throws exactly a ThatDelegate.CustomException within 0:05, + but it was + """); + } + + [Theory] + [AutoData] + public async Task WhenSupertypeExceptionIsThrown_ShouldFail(string message) + { + Exception exception = new CustomException(message); + Action action = () => throw exception; + + async Task Act() + => await That(action).ThrowsExactly().Within(6.Seconds()); + + await That(Act).ThrowsException() + .WithMessage($""" + Expected that action + throws exactly a ThatDelegate.SubCustomException within 0:06, + but it did throw a ThatDelegate.CustomException: + {message} + """); + } + } + + public sealed class TypeTests + { + [Fact] + public async Task ShouldSupportChainedConstraints() + { + Action action = () => { }; + + async Task Act() + => await That(action).ThrowsExactly(typeof(ArgumentException)).Within(5.Seconds()) + .WithMessage("foo"); + + await That(Act).ThrowsException() + .WithMessage(""" + Expected that action + throws exactly an ArgumentException within 0:05 with Message equal to "foo", + but it did not throw any exception + """); + } + + [Theory] + [AutoData] + public async Task WhenAwaited_ShouldReturnThrownException(string value) + { + Exception exception = new CustomException + { + Value = value + }; + Action action = () => throw exception; + + Exception result = + await That(action).ThrowsExactly(typeof(CustomException)).Within(5.Seconds()); + + await That(result).IsSameAs(exception); + } + + [Fact] + public async Task WhenExactExceptionTypeIsThrownInTime_ShouldSucceed() + { + Exception exception = new CustomException(); + Action action = () => throw exception; + + async Task Act() + => await That(action).ThrowsExactly(typeof(CustomException)).Within(5.Seconds()); + + await That(Act).DoesNotThrow(); + } + + [Fact] + public async Task WhenExactExceptionTypeIsThrownTooLate_ShouldFail() + { + Exception exception = new CustomException(); + Action action = () => + { + Task.Delay(50.Milliseconds()).Wait(); + throw exception; + }; + + async Task Act() + => await That(action).ThrowsExactly(typeof(CustomException)).Within(5.Milliseconds()); + + await That(Act).ThrowsException() + .WithMessage(""" + Expected that action + throws exactly a ThatDelegate.CustomException within 0:00.005, + but it took * + """).AsWildcard(); + } + + [Fact] + public async Task WhenNoExceptionIsThrownAndExecutionTimeIsTooLarge_ShouldFail() + { + Action action = () => + { + Task.Delay(50.Milliseconds()).Wait(); + }; + + async Task Act() + => await That(action).ThrowsExactly(typeof(Exception)).Within(5.Milliseconds()); + + await That(Act).ThrowsException() + .WithMessage(""" + Expected that action + throws exactly an Exception within 0:00.005, + but it took * + """).AsWildcard(); + } + + [Fact] + public async Task WhenNoExceptionIsThrownInTime_ShouldFail() + { + Action action = () => { }; + + async Task Act() + => await That(action).ThrowsExactly(typeof(Exception)).Within(5.Seconds()).Because("it should"); + + await That(Act).ThrowsException() + .WithMessage(""" + Expected that action + throws exactly an Exception within 0:05, because it should, + but it did not throw any exception + """); + } + + [Theory] + [AutoData] + public async Task WhenOtherExceptionIsThrown_ShouldFail(string message) + { + Exception exception = new OtherException(message); + Action action = () => throw exception; + + async Task Act() + => await That(action).ThrowsExactly(typeof(CustomException)).Within(5.Seconds()); + + await That(Act).ThrowsException() + .WithMessage($""" + Expected that action + throws exactly a ThatDelegate.CustomException within 0:05, + but it did throw a ThatDelegate.OtherException: + {message} + """); + } + + [Fact] + public async Task WhenSubCustomExceptionIsThrown_ShouldFail() + { + Exception exception = new SubCustomException(); + Action action = () => throw exception; + + async Task Act() + => await That(action).ThrowsExactly(typeof(CustomException)).Within(5.Seconds()); + + await That(Act).ThrowsException() + .WithMessage(""" + Expected that action + throws exactly a ThatDelegate.CustomException within 0:05, + but it did throw a ThatDelegate.SubCustomException: + WhenSubCustomExceptionIsThrown_ShouldFail + """); + } + + [Fact] + public async Task WhenSubjectIsNull_ShouldFail() + { + Action? subject = null; + + async Task Act() + => await That(subject!).ThrowsExactly(typeof(CustomException)).Within(5.Seconds()); + + await That(Act).ThrowsExactly() + .WithMessage(""" + Expected that subject + throws exactly a ThatDelegate.CustomException within 0:05, + but it was + """); + } + + [Theory] + [AutoData] + public async Task WhenSupertypeExceptionIsThrown_ShouldFail(string message) + { + Exception exception = new CustomException(message); + Action action = () => throw exception; + + async Task Act() + => await That(action).ThrowsExactly(typeof(SubCustomException)).Within(6.Seconds()); + + await That(Act).ThrowsException() + .WithMessage($""" + Expected that action + throws exactly a ThatDelegate.SubCustomException within 0:06, + but it did throw a ThatDelegate.CustomException: + {message} + """); + } + } + } + } +} diff --git a/Tests/aweXpect.Tests/Delegates/ThatDelegate.ThrowsException.Within.Tests.cs b/Tests/aweXpect.Tests/Delegates/ThatDelegate.ThrowsException.Within.Tests.cs new file mode 100644 index 000000000..5ddf8887c --- /dev/null +++ b/Tests/aweXpect.Tests/Delegates/ThatDelegate.ThrowsException.Within.Tests.cs @@ -0,0 +1,129 @@ +namespace aweXpect.Tests; + +public sealed partial class ThatDelegate +{ + public sealed partial class ThrowsException + { + public sealed class Within + { + public sealed class Tests + { + [Fact] + public async Task ShouldSupportChainedConstraints() + { + Action action = () => { }; + + async Task Act() + => await That(action).ThrowsException().Within(5.Seconds()).WithMessage("foo"); + + await That(Act).ThrowsException() + .WithMessage(""" + Expected that action + throws an exception within 0:05 with Message equal to "foo", + but it did not throw any exception + """); + } + + [Theory] + [AutoData] + public async Task WhenAwaited_ShouldReturnThrownException(string value) + { + Exception exception = new CustomException + { + Value = value + }; + Action action = () => throw exception; + + Exception result = + await That(action).ThrowsException().Within(5.Seconds()); + + await That(result).IsSameAs(exception); + } + + [Fact] + public async Task WhenExceptionTypeIsThrownInTime_ShouldSucceed() + { + Exception exception = new CustomException(); + Action action = () => throw exception; + + async Task Act() + => await That(action).ThrowsException().Within(5.Seconds()); + + await That(Act).DoesNotThrow(); + } + + [Fact] + public async Task WhenExceptionIsThrownTooLate_ShouldFail() + { + Exception exception = new CustomException(); + Action action = () => + { + Task.Delay(50.Milliseconds()).Wait(); + throw exception; + }; + + async Task Act() + => await That(action).ThrowsException().Within(5.Milliseconds()); + + await That(Act).ThrowsException() + .WithMessage(""" + Expected that action + throws an exception within 0:00.005, + but it took * + """).AsWildcard(); + } + + [Fact] + public async Task WhenNoExceptionIsThrownAndExecutionTimeIsTooLarge_ShouldFail() + { + Action action = () => + { + Task.Delay(50.Milliseconds()).Wait(); + }; + + async Task Act() + => await That(action).ThrowsException().Within(5.Milliseconds()); + + await That(Act).ThrowsException() + .WithMessage(""" + Expected that action + throws an exception within 0:00.005, + but it took * + """).AsWildcard(); + } + + [Fact] + public async Task WhenNoExceptionIsThrownInTime_ShouldFail() + { + Action action = () => { }; + + async Task Act() + => await That(action).ThrowsException().Within(5.Seconds()).Because("it should"); + + await That(Act).ThrowsException() + .WithMessage(""" + Expected that action + throws an exception within 0:05, because it should, + but it did not throw any exception + """); + } + + [Fact] + public async Task WhenSubjectIsNull_ShouldFail() + { + Action? subject = null; + + async Task Act() + => await That(subject!).ThrowsException().Within(5.Seconds()); + + await That(Act).Throws() + .WithMessage(""" + Expected that subject + throws an exception within 0:05, + but it was + """); + } + } + } + } +}