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
+ """);
+ }
+ }
+ }
+ }
+}