diff --git a/Directory.Packages.props b/Directory.Packages.props
index 65bbcc0..a7e0e91 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -4,8 +4,9 @@
-
-
+
+
+
diff --git a/README.md b/README.md
index edd7356..a5dcc32 100644
--- a/README.md
+++ b/README.md
@@ -30,6 +30,39 @@ await That(mock.VerifyMock.Invoked.MyMethod()).AtMost(4.Times()); // At most 4
await That(mock.VerifyMock.Invoked.MyMethod()).Exactly(2.Times()); // Exactly 2 times
```
+#### Asynchronous verification
+
+With `Within(TimeSpan timeout)`, you can check whether the expected number of calls occurred within a given time
+interval. This is useful for asynchronous or delayed invocations in the background.
+
+```csharp
+var mock = Mock.Create();
+
+// Start asynchronous calls, e.g., in a Task
+Task.Run(async () =>
+{
+ await Task.Delay(500);
+ mock.MyMethod();
+});
+
+// Verifies that MyMethod was called at least once within 1 second
+await That(mock.VerifyMock.Invoked.MyMethod())
+ .AtLeastOnce()
+ .Within(TimeSpan.FromSeconds(1));
+```
+
+Instead of a fixed time span, you can also provide a `CancellationToken` to limit how long the verification should wait
+for the expected interactions:
+
+```csharp
+var token = new CancellationTokenSource(TimeSpan.FromSeconds(1)).Token;
+
+// Verifies that MyMethod was called at least once within 1 second
+await That(mock.VerifyMock.Invoked.MyMethod())
+ .AtLeastOnce()
+ .WithCancellation(token);
+```
+
### Interaction order
Verify that methods were called in a specific sequence:
diff --git a/Source/aweXpect.Mockolate/Options/WithinOptions.cs b/Source/aweXpect.Mockolate/Options/WithinOptions.cs
new file mode 100644
index 0000000..a95cec1
--- /dev/null
+++ b/Source/aweXpect.Mockolate/Options/WithinOptions.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Threading;
+
+namespace aweXpect.Options;
+
+///
+/// The options for a verification result which allows specifying a timeout and/or cancellation token for the
+/// verification.
+///
+public class WithinOptions
+{
+ ///
+ /// The timeout that is applied to the verification.
+ ///
+ public TimeSpan? Timeout { get; set; }
+
+ ///
+ /// The cancellation token that is used to cancel the verification.
+ ///
+ public CancellationToken? CancellationToken { get; set; }
+}
diff --git a/Source/aweXpect.Mockolate/Results/AndOrWithinResult.cs b/Source/aweXpect.Mockolate/Results/AndOrWithinResult.cs
new file mode 100644
index 0000000..7b8948e
--- /dev/null
+++ b/Source/aweXpect.Mockolate/Results/AndOrWithinResult.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Threading;
+using aweXpect.Core;
+using aweXpect.Options;
+
+namespace aweXpect.Results;
+
+///
+/// The result of a verification result which allows specifying a timeout and/or cancellation token for the
+/// verification.
+///
+///
+///
+///
+public class AndOrWithinResult(
+ ExpectationBuilder expectationBuilder,
+ TThat returnValue,
+ WithinOptions options)
+ : AndOrWithinResult>(
+ expectationBuilder,
+ returnValue,
+ options);
+
+///
+public class AndOrWithinResult : AndOrResult
+ where TSelf : AndOrWithinResult
+{
+ private readonly WithinOptions _options;
+
+ ///
+ protected AndOrWithinResult(ExpectationBuilder expectationBuilder, TThat returnValue, WithinOptions options)
+ : base(expectationBuilder, returnValue)
+ {
+ _options = options;
+ }
+
+ ///
+ /// …within the given .
+ ///
+ public TSelf Within(TimeSpan timeout)
+ {
+ _options.Timeout = timeout;
+ return (TSelf)this;
+ }
+
+ ///
+ /// …with the given .
+ ///
+ public new TSelf WithCancellation(CancellationToken cancellationToken)
+ {
+ _options.CancellationToken = cancellationToken;
+ base.WithCancellation(cancellationToken);
+ return (TSelf)this;
+ }
+}
diff --git a/Source/aweXpect.Mockolate/ThatVerificationResult.AtLeast.cs b/Source/aweXpect.Mockolate/ThatVerificationResult.AtLeast.cs
index 2c4c361..664f8f2 100644
--- a/Source/aweXpect.Mockolate/ThatVerificationResult.AtLeast.cs
+++ b/Source/aweXpect.Mockolate/ThatVerificationResult.AtLeast.cs
@@ -1,5 +1,6 @@
using aweXpect.Core;
using aweXpect.Helpers;
+using aweXpect.Options;
using aweXpect.Results;
using Mockolate.Verify;
@@ -8,11 +9,16 @@ namespace aweXpect;
public static partial class ThatVerificationResult
{
///
- /// Verifies that the checked interaction happened at least the number of .
+ /// Verifies that the checked interaction happened at least the number of .
///
- public static AndOrResult, IThat>> AtLeast(
- this IThat> subject, Times times)
- => new(subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
- => new HasAtLeastConstraint(expectationBuilder, it, grammars, times.Value)),
- subject);
+ public static AndOrWithinResult, IThat>>
+ AtLeast(this IThat> subject, Times times)
+ {
+ WithinOptions options = new();
+ return new AndOrWithinResult, IThat>>(
+ subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
+ => new HasAtLeastConstraint(expectationBuilder, it, grammars, times.Value, options)),
+ subject,
+ options);
+ }
}
diff --git a/Source/aweXpect.Mockolate/ThatVerificationResult.AtLeastOnce.cs b/Source/aweXpect.Mockolate/ThatVerificationResult.AtLeastOnce.cs
index 84b2aad..e26c7e7 100644
--- a/Source/aweXpect.Mockolate/ThatVerificationResult.AtLeastOnce.cs
+++ b/Source/aweXpect.Mockolate/ThatVerificationResult.AtLeastOnce.cs
@@ -1,5 +1,6 @@
using aweXpect.Core;
using aweXpect.Helpers;
+using aweXpect.Options;
using aweXpect.Results;
using Mockolate.Verify;
@@ -10,9 +11,14 @@ public static partial class ThatVerificationResult
///
/// Verifies that the checked interaction happened at least once.
///
- public static AndOrResult, IThat>> AtLeastOnce(
- this IThat> subject)
- => new(subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
- => new HasAtLeastConstraint(expectationBuilder, it, grammars, 1)),
- subject);
+ public static AndOrWithinResult, IThat>>
+ AtLeastOnce(this IThat> subject)
+ {
+ WithinOptions options = new();
+ return new AndOrWithinResult, IThat>>(
+ subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
+ => new HasAtLeastConstraint(expectationBuilder, it, grammars, 1, options)),
+ subject,
+ options);
+ }
}
diff --git a/Source/aweXpect.Mockolate/ThatVerificationResult.AtLeastTwice.cs b/Source/aweXpect.Mockolate/ThatVerificationResult.AtLeastTwice.cs
index f70926e..a35151f 100644
--- a/Source/aweXpect.Mockolate/ThatVerificationResult.AtLeastTwice.cs
+++ b/Source/aweXpect.Mockolate/ThatVerificationResult.AtLeastTwice.cs
@@ -1,5 +1,6 @@
using aweXpect.Core;
using aweXpect.Helpers;
+using aweXpect.Options;
using aweXpect.Results;
using Mockolate.Verify;
@@ -10,9 +11,14 @@ public static partial class ThatVerificationResult
///
/// Verifies that the checked interaction happened at least twice.
///
- public static AndOrResult, IThat>> AtLeastTwice(
- this IThat> subject)
- => new(subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
- => new HasAtLeastConstraint(expectationBuilder, it, grammars, 2)),
- subject);
+ public static AndOrWithinResult, IThat>>
+ AtLeastTwice(this IThat> subject)
+ {
+ WithinOptions options = new();
+ return new AndOrWithinResult, IThat>>(
+ subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
+ => new HasAtLeastConstraint(expectationBuilder, it, grammars, 2, options)),
+ subject,
+ options);
+ }
}
diff --git a/Source/aweXpect.Mockolate/ThatVerificationResult.AtMost.cs b/Source/aweXpect.Mockolate/ThatVerificationResult.AtMost.cs
index 178a8d6..0c4e49d 100644
--- a/Source/aweXpect.Mockolate/ThatVerificationResult.AtMost.cs
+++ b/Source/aweXpect.Mockolate/ThatVerificationResult.AtMost.cs
@@ -8,10 +8,10 @@ namespace aweXpect;
public static partial class ThatVerificationResult
{
///
- /// Verifies that the checked interaction happened at most the number of .
+ /// Verifies that the checked interaction happened at most the number of .
///
- public static AndOrResult, IThat>> AtMost(
- this IThat> subject, Times times)
+ public static AndOrResult, IThat>>
+ AtMost(this IThat> subject, Times times)
=> new(subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
=> new HasAtMostConstraint(expectationBuilder, it, grammars, times.Value)),
subject);
diff --git a/Source/aweXpect.Mockolate/ThatVerificationResult.AtMostOnce.cs b/Source/aweXpect.Mockolate/ThatVerificationResult.AtMostOnce.cs
index 8cf432e..09766ae 100644
--- a/Source/aweXpect.Mockolate/ThatVerificationResult.AtMostOnce.cs
+++ b/Source/aweXpect.Mockolate/ThatVerificationResult.AtMostOnce.cs
@@ -10,8 +10,8 @@ public static partial class ThatVerificationResult
///
/// Verifies that the checked interaction happened at most once.
///
- public static AndOrResult, IThat>> AtMostOnce(
- this IThat> subject)
+ public static AndOrResult, IThat>>
+ AtMostOnce(this IThat> subject)
=> new(subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
=> new HasAtMostConstraint(expectationBuilder, it, grammars, 1)),
subject);
diff --git a/Source/aweXpect.Mockolate/ThatVerificationResult.AtMostTwice.cs b/Source/aweXpect.Mockolate/ThatVerificationResult.AtMostTwice.cs
index ff22e20..cf92019 100644
--- a/Source/aweXpect.Mockolate/ThatVerificationResult.AtMostTwice.cs
+++ b/Source/aweXpect.Mockolate/ThatVerificationResult.AtMostTwice.cs
@@ -10,8 +10,8 @@ public static partial class ThatVerificationResult
///
/// Verifies that the checked interaction happened at most twice.
///
- public static AndOrResult, IThat>> AtMostTwice(
- this IThat> subject)
+ public static AndOrResult, IThat>>
+ AtMostTwice(this IThat> subject)
=> new(subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
=> new HasAtMostConstraint(expectationBuilder, it, grammars, 2)),
subject);
diff --git a/Source/aweXpect.Mockolate/ThatVerificationResult.Between.cs b/Source/aweXpect.Mockolate/ThatVerificationResult.Between.cs
index 5f5844d..66a0d51 100644
--- a/Source/aweXpect.Mockolate/ThatVerificationResult.Between.cs
+++ b/Source/aweXpect.Mockolate/ThatVerificationResult.Between.cs
@@ -1,8 +1,11 @@
using System.Diagnostics.CodeAnalysis;
using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
using aweXpect.Core;
using aweXpect.Core.Constraints;
using aweXpect.Helpers;
+using aweXpect.Options;
using aweXpect.Results;
using Mockolate.Verify;
@@ -13,28 +16,69 @@ public static partial class ThatVerificationResult
///
/// Verifies that the checked interaction happened between …
///
- public static BetweenResult, IThat>>, Times>
- Between(
- this IThat> subject, int minimum)
- => new(maximum => new AndOrResult, IThat>>(subject.Get()
- .ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
- => new HasBetweenConstraint(expectationBuilder, it, grammars, minimum, maximum.Value)),
- subject));
-
+ public static BetweenResult, IThat>>,
+ Times>
+ Between(this IThat> subject, int minimum)
+ {
+ WithinOptions options = new();
+ return new
+ BetweenResult, IThat>>,
+ Times>(maximum
+ => new AndOrWithinResult, IThat>>(
+ subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
+ => new HasBetweenConstraint(expectationBuilder, it, grammars, minimum,
+ maximum.Value, options)),
+ subject,
+ options));
+ }
+
private sealed class HasBetweenConstraint(
ExpectationBuilder expectationBuilder,
string it,
ExpectationGrammars grammars,
int minimum,
- int maximum)
+ int maximum,
+ WithinOptions options)
: ConstraintResult.WithValue>(grammars),
- IValueConstraint>
+ IAsyncConstraint>
{
private int _count = -1;
private string _expectation = "";
- public ConstraintResult IsMetBy(VerificationResult actual)
+ public async Task IsMetBy(VerificationResult actual,
+ CancellationToken cancellationToken)
{
+ if (options.CancellationToken is not null)
+ {
+ actual = actual.WithCancellation(options.CancellationToken.Value);
+ }
+
+ if (options.Timeout is not null)
+ {
+ actual = actual.Within(options.Timeout.Value);
+ }
+ else if (expectationBuilder.Timeout is not null)
+ {
+ actual = actual.Within(expectationBuilder.Timeout.Value);
+ }
+
+ if (actual is IAsyncVerificationResult asyncVerificationResult)
+ {
+ _expectation = asyncVerificationResult.Expectation;
+ Actual = actual;
+ Outcome = await asyncVerificationResult.VerifyAsync(interactions =>
+ {
+ string context = Formatter.Format(interactions, FormattingOptions.MultipleLines);
+ expectationBuilder.UpdateContexts(contexts => contexts.Add(
+ new ResultContext.SyncCallback("Interactions", () => context)));
+ _count = interactions.Length;
+ return interactions.Length >= minimum && interactions.Length <= maximum;
+ })
+ ? Outcome.Success
+ : Outcome.Failure;
+ return this;
+ }
+
IVerificationResult result = actual;
_expectation = result.Expectation;
Actual = actual;
diff --git a/Source/aweXpect.Mockolate/ThatVerificationResult.Exactly.cs b/Source/aweXpect.Mockolate/ThatVerificationResult.Exactly.cs
index 97ae108..89d2c91 100644
--- a/Source/aweXpect.Mockolate/ThatVerificationResult.Exactly.cs
+++ b/Source/aweXpect.Mockolate/ThatVerificationResult.Exactly.cs
@@ -1,5 +1,6 @@
using aweXpect.Core;
using aweXpect.Helpers;
+using aweXpect.Options;
using aweXpect.Results;
using Mockolate.Verify;
@@ -8,11 +9,16 @@ namespace aweXpect;
public static partial class ThatVerificationResult
{
///
- /// Verifies that the checked interaction happened exactly the number of .
+ /// Verifies that the checked interaction happened exactly the number of .
///
- public static AndOrResult, IThat>> Exactly(
- this IThat> subject, Times times)
- => new(subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
- => new HasExactlyConstraint(expectationBuilder, it, grammars, times.Value)),
- subject);
+ public static AndOrWithinResult, IThat>>
+ Exactly(this IThat> subject, Times times)
+ {
+ WithinOptions options = new();
+ return new AndOrWithinResult, IThat>>(
+ subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
+ => new HasExactlyConstraint(expectationBuilder, it, grammars, times.Value, options)),
+ subject,
+ options);
+ }
}
diff --git a/Source/aweXpect.Mockolate/ThatVerificationResult.Never.cs b/Source/aweXpect.Mockolate/ThatVerificationResult.Never.cs
index 16bf94d..22e265e 100644
--- a/Source/aweXpect.Mockolate/ThatVerificationResult.Never.cs
+++ b/Source/aweXpect.Mockolate/ThatVerificationResult.Never.cs
@@ -1,5 +1,6 @@
using aweXpect.Core;
using aweXpect.Helpers;
+using aweXpect.Options;
using aweXpect.Results;
using Mockolate.Verify;
@@ -10,9 +11,13 @@ public static partial class ThatVerificationResult
///
/// Verifies that the checked interaction happened never.
///
- public static AndOrResult, IThat>> Never(
- this IThat> subject)
- => new(subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
- => new HasExactlyConstraint(expectationBuilder, it, grammars, 0)),
+ public static AndOrResult, IThat>>
+ Never(this IThat> subject)
+ {
+ WithinOptions options = new();
+ return new AndOrResult, IThat>>(
+ subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
+ => new HasExactlyConstraint(expectationBuilder, it, grammars, 0, options)),
subject);
+ }
}
diff --git a/Source/aweXpect.Mockolate/ThatVerificationResult.Once.cs b/Source/aweXpect.Mockolate/ThatVerificationResult.Once.cs
index a9013e6..b0fe63d 100644
--- a/Source/aweXpect.Mockolate/ThatVerificationResult.Once.cs
+++ b/Source/aweXpect.Mockolate/ThatVerificationResult.Once.cs
@@ -1,5 +1,6 @@
using aweXpect.Core;
using aweXpect.Helpers;
+using aweXpect.Options;
using aweXpect.Results;
using Mockolate.Verify;
@@ -10,9 +11,14 @@ public static partial class ThatVerificationResult
///
/// Verifies that the checked interaction happened exactly once.
///
- public static AndOrResult, IThat>> Once(
- this IThat> subject)
- => new(subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
- => new HasExactlyConstraint(expectationBuilder, it, grammars, 1)),
- subject);
+ public static AndOrWithinResult, IThat>>
+ Once(this IThat> subject)
+ {
+ WithinOptions options = new();
+ return new AndOrWithinResult, IThat>>(
+ subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
+ => new HasExactlyConstraint(expectationBuilder, it, grammars, 1, options)),
+ subject,
+ options);
+ }
}
diff --git a/Source/aweXpect.Mockolate/ThatVerificationResult.Times.cs b/Source/aweXpect.Mockolate/ThatVerificationResult.Times.cs
index 08b068f..c4f9923 100644
--- a/Source/aweXpect.Mockolate/ThatVerificationResult.Times.cs
+++ b/Source/aweXpect.Mockolate/ThatVerificationResult.Times.cs
@@ -2,9 +2,12 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
using aweXpect.Core;
using aweXpect.Core.Constraints;
using aweXpect.Helpers;
+using aweXpect.Options;
using aweXpect.Results;
using Mockolate.Verify;
@@ -15,29 +18,67 @@ public static partial class ThatVerificationResult
///
/// Verifies that the checked interaction happened according to the .
///
- public static AndOrResult, IThat>> Times(
+ public static AndOrWithinResult, IThat>> Times(
this IThat> subject, Func predicate,
- [CallerArgumentExpression("predicate")] string doNotPopulateThisValue = "")
- => new(subject.Get()
- .ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
- => new TimesConstraint(expectationBuilder, it, grammars, predicate,
- doNotPopulateThisValue)),
- subject);
+ [CallerArgumentExpression("predicate")]
+ string doNotPopulateThisValue = "")
+ {
+ WithinOptions options = new();
+ return new AndOrWithinResult, IThat>>(
+ subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
+ => new TimesConstraint(expectationBuilder, it, grammars, predicate, doNotPopulateThisValue,
+ options)),
+ subject,
+ options);
+ }
private sealed class TimesConstraint(
ExpectationBuilder expectationBuilder,
string it,
ExpectationGrammars grammars,
Func predicate,
- string predicateExpression)
+ string predicateExpression,
+ WithinOptions options)
: ConstraintResult.WithValue>(grammars),
- IValueConstraint>
+ IAsyncConstraint>
{
private int _count = -1;
private string _expectation = "";
- public ConstraintResult IsMetBy(VerificationResult actual)
+ public async Task IsMetBy(VerificationResult actual,
+ CancellationToken cancellationToken)
{
+ if (options.CancellationToken is not null)
+ {
+ actual = actual.WithCancellation(options.CancellationToken.Value);
+ }
+
+ if (options.Timeout is not null)
+ {
+ actual = actual.Within(options.Timeout.Value);
+ }
+ else if (expectationBuilder.Timeout is not null)
+ {
+ actual = actual.Within(expectationBuilder.Timeout.Value);
+ }
+
+ if (actual is IAsyncVerificationResult asyncVerificationResult)
+ {
+ _expectation = asyncVerificationResult.Expectation;
+ Actual = actual;
+ Outcome = await asyncVerificationResult.VerifyAsync(interactions =>
+ {
+ string context = Formatter.Format(interactions, FormattingOptions.MultipleLines);
+ expectationBuilder.UpdateContexts(contexts => contexts.Add(
+ new ResultContext.SyncCallback("Interactions", () => context)));
+ _count = interactions.Length;
+ return predicate(_count);
+ })
+ ? Outcome.Success
+ : Outcome.Failure;
+ return this;
+ }
+
IVerificationResult result = actual;
_expectation = result.Expectation;
Actual = actual;
diff --git a/Source/aweXpect.Mockolate/ThatVerificationResult.Twice.cs b/Source/aweXpect.Mockolate/ThatVerificationResult.Twice.cs
index cf4c8df..47f1629 100644
--- a/Source/aweXpect.Mockolate/ThatVerificationResult.Twice.cs
+++ b/Source/aweXpect.Mockolate/ThatVerificationResult.Twice.cs
@@ -1,5 +1,6 @@
using aweXpect.Core;
using aweXpect.Helpers;
+using aweXpect.Options;
using aweXpect.Results;
using Mockolate.Verify;
@@ -10,9 +11,14 @@ public static partial class ThatVerificationResult
///
/// Verifies that the checked interaction happened exactly twice.
///
- public static AndOrResult, IThat>> Twice(
+ public static AndOrWithinResult, IThat>> Twice(
this IThat> subject)
- => new(subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
- => new HasExactlyConstraint(expectationBuilder, it, grammars, 2)),
- subject);
+ {
+ WithinOptions options = new();
+ return new AndOrWithinResult, IThat>>(
+ subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
+ => new HasExactlyConstraint(expectationBuilder, it, grammars, 2, options)),
+ subject,
+ options);
+ }
}
diff --git a/Source/aweXpect.Mockolate/ThatVerificationResult.cs b/Source/aweXpect.Mockolate/ThatVerificationResult.cs
index be3983a..c47b42e 100644
--- a/Source/aweXpect.Mockolate/ThatVerificationResult.cs
+++ b/Source/aweXpect.Mockolate/ThatVerificationResult.cs
@@ -1,8 +1,11 @@
using System.Diagnostics.CodeAnalysis;
using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
using aweXpect.Core;
using aweXpect.Core.Constraints;
using aweXpect.Helpers;
+using aweXpect.Options;
using Mockolate.Verify;
namespace aweXpect;
@@ -12,19 +15,61 @@ namespace aweXpect;
///
public static partial class ThatVerificationResult
{
+ private static string ToAmountString(this int number)
+ => number switch
+ {
+ 0 => "never",
+ 1 => "once",
+ 2 => "twice",
+ _ => $"{number} times",
+ };
+
private sealed class HasExactlyConstraint(
ExpectationBuilder expectationBuilder,
string it,
ExpectationGrammars grammars,
- int expected)
+ int expected,
+ WithinOptions options)
: ConstraintResult.WithValue>(grammars),
- IValueConstraint>
+ IAsyncConstraint>
{
private int _count = -1;
private string _expectation = "";
- public ConstraintResult IsMetBy(VerificationResult actual)
+ public async Task IsMetBy(VerificationResult actual,
+ CancellationToken cancellationToken)
{
+ if (options.CancellationToken is not null)
+ {
+ actual = actual.WithCancellation(options.CancellationToken.Value);
+ }
+
+ if (options.Timeout is not null)
+ {
+ actual = actual.Within(options.Timeout.Value);
+ }
+ else if (expectationBuilder.Timeout is not null)
+ {
+ actual = actual.Within(expectationBuilder.Timeout.Value);
+ }
+
+ if (actual is IAsyncVerificationResult asyncVerificationResult)
+ {
+ _expectation = asyncVerificationResult.Expectation;
+ Actual = actual;
+ Outcome = await asyncVerificationResult.VerifyAsync(interactions =>
+ {
+ string context = Formatter.Format(interactions, FormattingOptions.MultipleLines);
+ expectationBuilder.UpdateContexts(contexts => contexts.Add(
+ new ResultContext.SyncCallback("Interactions", () => context)));
+ _count = interactions.Length;
+ return interactions.Length == expected;
+ })
+ ? Outcome.Success
+ : Outcome.Failure;
+ return this;
+ }
+
IVerificationResult result = actual;
_expectation = result.Expectation;
Actual = actual;
@@ -61,7 +106,8 @@ protected override void AppendNormalResult(StringBuilder stringBuilder, string?
}
else
{
- stringBuilder.Append("found ").Append(it).Append(_count < expected ? " only " : " ").Append(_count.ToAmountString());
+ stringBuilder.Append("found ").Append(it).Append(_count < expected ? " only " : " ")
+ .Append(_count.ToAmountString());
}
}
@@ -83,7 +129,7 @@ protected override void AppendNegatedResult(StringBuilder stringBuilder, string?
public override bool TryGetValue([NotNullWhen(true)] out TValue? value) where TValue : default
{
if (typeof(TValue) == typeof(IDescribableSubject) &&
- new MyDescribableSubject() is TValue describableSubject)
+ new MyDescribableSubject() is TValue describableSubject)
{
value = describableSubject;
return true;
@@ -123,19 +169,13 @@ public ConstraintResult IsMetBy(VerificationResult actual)
}
protected override void AppendNormalExpectation(StringBuilder stringBuilder, string? indentation = null)
- {
- stringBuilder.Append(_expectation).Append(" at most ").Append(expected.ToAmountString());
- }
+ => stringBuilder.Append(_expectation).Append(" at most ").Append(expected.ToAmountString());
protected override void AppendNormalResult(StringBuilder stringBuilder, string? indentation = null)
- {
- stringBuilder.Append("found ").Append(it).Append(' ').Append(_count.ToAmountString());
- }
+ => stringBuilder.Append("found ").Append(it).Append(' ').Append(_count.ToAmountString());
protected override void AppendNegatedExpectation(StringBuilder stringBuilder, string? indentation = null)
- {
- stringBuilder.Append(_expectation).Append(" more than ").Append(expected.ToAmountString());
- }
+ => stringBuilder.Append(_expectation).Append(" more than ").Append(expected.ToAmountString());
protected override void AppendNegatedResult(StringBuilder stringBuilder, string? indentation = null)
{
@@ -152,7 +192,7 @@ protected override void AppendNegatedResult(StringBuilder stringBuilder, string?
public override bool TryGetValue([NotNullWhen(true)] out TValue? value) where TValue : default
{
if (typeof(TValue) == typeof(IDescribableSubject) &&
- new MyDescribableSubject() is TValue describableSubject)
+ new MyDescribableSubject() is TValue describableSubject)
{
value = describableSubject;
return true;
@@ -166,15 +206,48 @@ private sealed class HasAtLeastConstraint(
ExpectationBuilder expectationBuilder,
string it,
ExpectationGrammars grammars,
- int expected)
+ int expected,
+ WithinOptions options)
: ConstraintResult.WithValue>(grammars),
- IValueConstraint>
+ IAsyncConstraint>
{
private int _count = -1;
private string _expectation = "";
- public ConstraintResult IsMetBy(VerificationResult actual)
+ public async Task IsMetBy(VerificationResult actual,
+ CancellationToken cancellationToken)
{
+ if (options.CancellationToken is not null)
+ {
+ actual = actual.WithCancellation(options.CancellationToken.Value);
+ }
+
+ if (options.Timeout is not null)
+ {
+ actual = actual.Within(options.Timeout.Value);
+ }
+ else if (expectationBuilder.Timeout is not null)
+ {
+ actual = actual.Within(expectationBuilder.Timeout.Value);
+ }
+
+ if (actual is IAsyncVerificationResult asyncVerificationResult)
+ {
+ _expectation = asyncVerificationResult.Expectation;
+ Actual = actual;
+ Outcome = await asyncVerificationResult.VerifyAsync(interactions =>
+ {
+ string context = Formatter.Format(interactions, FormattingOptions.MultipleLines);
+ expectationBuilder.UpdateContexts(contexts => contexts.Add(
+ new ResultContext.SyncCallback("Interactions", () => context)));
+ _count = interactions.Length;
+ return interactions.Length >= expected;
+ })
+ ? Outcome.Success
+ : Outcome.Failure;
+ return this;
+ }
+
IVerificationResult result = actual;
_expectation = result.Expectation;
Actual = actual;
@@ -192,9 +265,7 @@ public ConstraintResult IsMetBy(VerificationResult actual)
}
protected override void AppendNormalExpectation(StringBuilder stringBuilder, string? indentation = null)
- {
- stringBuilder.Append(_expectation).Append(" at least ").Append(expected.ToAmountString());
- }
+ => stringBuilder.Append(_expectation).Append(" at least ").Append(expected.ToAmountString());
protected override void AppendNormalResult(StringBuilder stringBuilder, string? indentation = null)
{
@@ -209,19 +280,15 @@ protected override void AppendNormalResult(StringBuilder stringBuilder, string?
}
protected override void AppendNegatedExpectation(StringBuilder stringBuilder, string? indentation = null)
- {
- stringBuilder.Append(_expectation).Append(" less than ").Append(expected.ToAmountString());
- }
+ => stringBuilder.Append(_expectation).Append(" less than ").Append(expected.ToAmountString());
protected override void AppendNegatedResult(StringBuilder stringBuilder, string? indentation = null)
- {
- stringBuilder.Append("found ").Append(it).Append(' ').Append(_count.ToAmountString());
- }
+ => stringBuilder.Append("found ").Append(it).Append(' ').Append(_count.ToAmountString());
public override bool TryGetValue([NotNullWhen(true)] out TValue? value) where TValue : default
{
if (typeof(TValue) == typeof(IDescribableSubject) &&
- new MyDescribableSubject() is TValue describableSubject)
+ new MyDescribableSubject() is TValue describableSubject)
{
value = describableSubject;
return true;
@@ -230,13 +297,4 @@ protected override void AppendNegatedResult(StringBuilder stringBuilder, string?
return base.TryGetValue(out value);
}
}
-
- private static string ToAmountString(this int number)
- => number switch
- {
- 0 => "never",
- 1 => "once",
- 2 => "twice",
- _ => $"{number} times"
- };
}
diff --git a/Tests/Directory.Build.props b/Tests/Directory.Build.props
index 31fac0d..2bad6a7 100644
--- a/Tests/Directory.Build.props
+++ b/Tests/Directory.Build.props
@@ -15,7 +15,7 @@
false
true
False
- 701;1702;CA1845
+ 701;1702;CA1845;xUnit1051
@@ -44,6 +44,7 @@
all
+
diff --git a/Tests/aweXpect.Mockolate.Api.Tests/Expected/aweXpect.Mockolate_net10.0.txt b/Tests/aweXpect.Mockolate.Api.Tests/Expected/aweXpect.Mockolate_net10.0.txt
index cf47744..62da815 100644
--- a/Tests/aweXpect.Mockolate.Api.Tests/Expected/aweXpect.Mockolate_net10.0.txt
+++ b/Tests/aweXpect.Mockolate.Api.Tests/Expected/aweXpect.Mockolate_net10.0.txt
@@ -16,6 +16,29 @@ namespace Mockolate.Web
}
}
}
+namespace aweXpect.Options
+{
+ public class WithinOptions
+ {
+ public WithinOptions() { }
+ public System.Threading.CancellationToken? CancellationToken { get; set; }
+ public System.TimeSpan? Timeout { get; set; }
+ }
+}
+namespace aweXpect.Results
+{
+ public class AndOrWithinResult : aweXpect.Results.AndOrWithinResult>
+ {
+ public AndOrWithinResult(aweXpect.Core.ExpectationBuilder expectationBuilder, TThat returnValue, aweXpect.Options.WithinOptions options) { }
+ }
+ public class AndOrWithinResult : aweXpect.Results.AndOrResult
+ where TSelf : aweXpect.Results.AndOrWithinResult
+ {
+ protected AndOrWithinResult(aweXpect.Core.ExpectationBuilder expectationBuilder, TThat returnValue, aweXpect.Options.WithinOptions options) { }
+ public TSelf WithCancellation(System.Threading.CancellationToken cancellationToken) { }
+ public TSelf Within(System.TimeSpan timeout) { }
+ }
+}
namespace aweXpect
{
public static class ThatMockVerify
@@ -25,18 +48,18 @@ namespace aweXpect
}
public static class ThatVerificationResult
{
- public static aweXpect.Results.AndOrResult, aweXpect.Core.IThat>> AtLeast(this aweXpect.Core.IThat> subject, aweXpect.Core.Times times) { }
- public static aweXpect.Results.AndOrResult, aweXpect.Core.IThat>> AtLeastOnce(this aweXpect.Core.IThat> subject) { }
- public static aweXpect.Results.AndOrResult, aweXpect.Core.IThat>> AtLeastTwice(this aweXpect.Core.IThat> subject) { }
+ public static aweXpect.Results.AndOrWithinResult, aweXpect.Core.IThat>> AtLeast(this aweXpect.Core.IThat> subject, aweXpect.Core.Times times) { }
+ public static aweXpect.Results.AndOrWithinResult, aweXpect.Core.IThat>> AtLeastOnce(this aweXpect.Core.IThat> subject) { }
+ public static aweXpect.Results.AndOrWithinResult, aweXpect.Core.IThat>> AtLeastTwice(this aweXpect.Core.IThat> subject) { }
public static aweXpect.Results.AndOrResult, aweXpect.Core.IThat>> AtMost(this aweXpect.Core.IThat> subject, aweXpect.Core.Times times) { }
public static aweXpect.Results.AndOrResult, aweXpect.Core.IThat>> AtMostOnce(this aweXpect.Core.IThat> subject) { }
public static aweXpect.Results.AndOrResult, aweXpect.Core.IThat>> AtMostTwice(this aweXpect.Core.IThat> subject) { }
- public static aweXpect.Results.BetweenResult, aweXpect.Core.IThat>>, aweXpect.Core.Times> Between(this aweXpect.Core.IThat> subject, int minimum) { }
- public static aweXpect.Results.AndOrResult, aweXpect.Core.IThat>> Exactly(this aweXpect.Core.IThat> subject, aweXpect.Core.Times times) { }
+ public static aweXpect.Results.BetweenResult, aweXpect.Core.IThat>>, aweXpect.Core.Times> Between(this aweXpect.Core.IThat> subject, int minimum) { }
+ public static aweXpect.Results.AndOrWithinResult, aweXpect.Core.IThat>> Exactly(this aweXpect.Core.IThat> subject, aweXpect.Core.Times times) { }
public static aweXpect.Results.AndOrResult, aweXpect.Core.IThat>> Never(this aweXpect.Core.IThat> subject) { }
- public static aweXpect.Results.AndOrResult, aweXpect.Core.IThat>> Once(this aweXpect.Core.IThat> subject) { }
+ public static aweXpect.Results.AndOrWithinResult, aweXpect.Core.IThat>> Once(this aweXpect.Core.IThat> subject) { }
public static aweXpect.Results.AndOrResult, aweXpect.Core.IThat>> Then(this aweXpect.Core.IThat> subject, params System.Func, Mockolate.Verify.VerificationResult>[] interactions) { }
- public static aweXpect.Results.AndOrResult, aweXpect.Core.IThat>> Times(this aweXpect.Core.IThat> subject, System.Func predicate, [System.Runtime.CompilerServices.CallerArgumentExpression("predicate")] string doNotPopulateThisValue = "") { }
- public static aweXpect.Results.AndOrResult, aweXpect.Core.IThat>> Twice(this aweXpect.Core.IThat> subject) { }
+ public static aweXpect.Results.AndOrWithinResult, aweXpect.Core.IThat>> Times(this aweXpect.Core.IThat> subject, System.Func predicate, [System.Runtime.CompilerServices.CallerArgumentExpression("predicate")] string doNotPopulateThisValue = "") { }
+ public static aweXpect.Results.AndOrWithinResult, aweXpect.Core.IThat>> Twice(this aweXpect.Core.IThat> subject) { }
}
}
\ No newline at end of file
diff --git a/Tests/aweXpect.Mockolate.Api.Tests/Expected/aweXpect.Mockolate_net8.0.txt b/Tests/aweXpect.Mockolate.Api.Tests/Expected/aweXpect.Mockolate_net8.0.txt
index 16772e8..eaccb70 100644
--- a/Tests/aweXpect.Mockolate.Api.Tests/Expected/aweXpect.Mockolate_net8.0.txt
+++ b/Tests/aweXpect.Mockolate.Api.Tests/Expected/aweXpect.Mockolate_net8.0.txt
@@ -16,6 +16,29 @@ namespace Mockolate.Web
}
}
}
+namespace aweXpect.Options
+{
+ public class WithinOptions
+ {
+ public WithinOptions() { }
+ public System.Threading.CancellationToken? CancellationToken { get; set; }
+ public System.TimeSpan? Timeout { get; set; }
+ }
+}
+namespace aweXpect.Results
+{
+ public class AndOrWithinResult : aweXpect.Results.AndOrWithinResult>
+ {
+ public AndOrWithinResult(aweXpect.Core.ExpectationBuilder expectationBuilder, TThat returnValue, aweXpect.Options.WithinOptions options) { }
+ }
+ public class AndOrWithinResult : aweXpect.Results.AndOrResult
+ where TSelf : aweXpect.Results.AndOrWithinResult
+ {
+ protected AndOrWithinResult(aweXpect.Core.ExpectationBuilder expectationBuilder, TThat returnValue, aweXpect.Options.WithinOptions options) { }
+ public TSelf WithCancellation(System.Threading.CancellationToken cancellationToken) { }
+ public TSelf Within(System.TimeSpan timeout) { }
+ }
+}
namespace aweXpect
{
public static class ThatMockVerify
@@ -25,18 +48,18 @@ namespace aweXpect
}
public static class ThatVerificationResult
{
- public static aweXpect.Results.AndOrResult, aweXpect.Core.IThat>> AtLeast(this aweXpect.Core.IThat> subject, aweXpect.Core.Times times) { }
- public static aweXpect.Results.AndOrResult, aweXpect.Core.IThat>> AtLeastOnce(this aweXpect.Core.IThat> subject) { }
- public static aweXpect.Results.AndOrResult, aweXpect.Core.IThat>> AtLeastTwice(this aweXpect.Core.IThat> subject) { }
+ public static aweXpect.Results.AndOrWithinResult, aweXpect.Core.IThat>> AtLeast(this aweXpect.Core.IThat> subject, aweXpect.Core.Times times) { }
+ public static aweXpect.Results.AndOrWithinResult, aweXpect.Core.IThat>> AtLeastOnce(this aweXpect.Core.IThat> subject) { }
+ public static aweXpect.Results.AndOrWithinResult, aweXpect.Core.IThat>> AtLeastTwice(this aweXpect.Core.IThat> subject) { }
public static aweXpect.Results.AndOrResult, aweXpect.Core.IThat>> AtMost(this aweXpect.Core.IThat> subject, aweXpect.Core.Times times) { }
public static aweXpect.Results.AndOrResult, aweXpect.Core.IThat>> AtMostOnce(this aweXpect.Core.IThat> subject) { }
public static aweXpect.Results.AndOrResult, aweXpect.Core.IThat>> AtMostTwice(this aweXpect.Core.IThat> subject) { }
- public static aweXpect.Results.BetweenResult, aweXpect.Core.IThat>>, aweXpect.Core.Times> Between(this aweXpect.Core.IThat> subject, int minimum) { }
- public static aweXpect.Results.AndOrResult, aweXpect.Core.IThat>> Exactly(this aweXpect.Core.IThat> subject, aweXpect.Core.Times times) { }
+ public static aweXpect.Results.BetweenResult, aweXpect.Core.IThat>>, aweXpect.Core.Times> Between(this aweXpect.Core.IThat> subject, int minimum) { }
+ public static aweXpect.Results.AndOrWithinResult, aweXpect.Core.IThat>> Exactly(this aweXpect.Core.IThat> subject, aweXpect.Core.Times times) { }
public static aweXpect.Results.AndOrResult, aweXpect.Core.IThat>> Never(this aweXpect.Core.IThat> subject) { }
- public static aweXpect.Results.AndOrResult, aweXpect.Core.IThat>> Once(this aweXpect.Core.IThat> subject) { }
+ public static aweXpect.Results.AndOrWithinResult, aweXpect.Core.IThat>> Once(this aweXpect.Core.IThat> subject) { }
public static aweXpect.Results.AndOrResult, aweXpect.Core.IThat>> Then(this aweXpect.Core.IThat> subject, params System.Func, Mockolate.Verify.VerificationResult>[] interactions) { }
- public static aweXpect.Results.AndOrResult, aweXpect.Core.IThat>> Times(this aweXpect.Core.IThat> subject, System.Func predicate, [System.Runtime.CompilerServices.CallerArgumentExpression("predicate")] string doNotPopulateThisValue = "") { }
- public static aweXpect.Results.AndOrResult, aweXpect.Core.IThat>> Twice(this aweXpect.Core.IThat> subject) { }
+ public static aweXpect.Results.AndOrWithinResult, aweXpect.Core.IThat>> Times(this aweXpect.Core.IThat> subject, System.Func predicate, [System.Runtime.CompilerServices.CallerArgumentExpression("predicate")] string doNotPopulateThisValue = "") { }
+ public static aweXpect.Results.AndOrWithinResult, aweXpect.Core.IThat>> Twice(this aweXpect.Core.IThat> subject) { }
}
}
\ No newline at end of file
diff --git a/Tests/aweXpect.Mockolate.Api.Tests/Expected/aweXpect.Mockolate_netstandard2.0.txt b/Tests/aweXpect.Mockolate.Api.Tests/Expected/aweXpect.Mockolate_netstandard2.0.txt
index 44bdb33..a22b11a 100644
--- a/Tests/aweXpect.Mockolate.Api.Tests/Expected/aweXpect.Mockolate_netstandard2.0.txt
+++ b/Tests/aweXpect.Mockolate.Api.Tests/Expected/aweXpect.Mockolate_netstandard2.0.txt
@@ -1,5 +1,28 @@
[assembly: System.Reflection.AssemblyMetadata("RepositoryUrl", "https://github.com/aweXpect/aweXpect.Mockolate.git")]
[assembly: System.Runtime.Versioning.TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName=".NET Standard 2.0")]
+namespace aweXpect.Options
+{
+ public class WithinOptions
+ {
+ public WithinOptions() { }
+ public System.Threading.CancellationToken? CancellationToken { get; set; }
+ public System.TimeSpan? Timeout { get; set; }
+ }
+}
+namespace aweXpect.Results
+{
+ public class AndOrWithinResult : aweXpect.Results.AndOrWithinResult>
+ {
+ public AndOrWithinResult(aweXpect.Core.ExpectationBuilder expectationBuilder, TThat returnValue, aweXpect.Options.WithinOptions options) { }
+ }
+ public class AndOrWithinResult : aweXpect.Results.AndOrResult
+ where TSelf : aweXpect.Results.AndOrWithinResult
+ {
+ protected AndOrWithinResult(aweXpect.Core.ExpectationBuilder expectationBuilder, TThat returnValue, aweXpect.Options.WithinOptions options) { }
+ public TSelf WithCancellation(System.Threading.CancellationToken cancellationToken) { }
+ public TSelf Within(System.TimeSpan timeout) { }
+ }
+}
namespace aweXpect
{
public static class ThatMockVerify
@@ -9,18 +32,18 @@ namespace aweXpect
}
public static class ThatVerificationResult
{
- public static aweXpect.Results.AndOrResult, aweXpect.Core.IThat>> AtLeast(this aweXpect.Core.IThat> subject, aweXpect.Core.Times times) { }
- public static aweXpect.Results.AndOrResult, aweXpect.Core.IThat>> AtLeastOnce(this aweXpect.Core.IThat> subject) { }
- public static aweXpect.Results.AndOrResult, aweXpect.Core.IThat>> AtLeastTwice(this aweXpect.Core.IThat> subject) { }
+ public static aweXpect.Results.AndOrWithinResult, aweXpect.Core.IThat>> AtLeast(this aweXpect.Core.IThat> subject, aweXpect.Core.Times times) { }
+ public static aweXpect.Results.AndOrWithinResult, aweXpect.Core.IThat>> AtLeastOnce(this aweXpect.Core.IThat> subject) { }
+ public static aweXpect.Results.AndOrWithinResult, aweXpect.Core.IThat>> AtLeastTwice(this aweXpect.Core.IThat> subject) { }
public static aweXpect.Results.AndOrResult, aweXpect.Core.IThat>> AtMost(this aweXpect.Core.IThat> subject, aweXpect.Core.Times times) { }
public static aweXpect.Results.AndOrResult, aweXpect.Core.IThat>> AtMostOnce(this aweXpect.Core.IThat> subject) { }
public static aweXpect.Results.AndOrResult, aweXpect.Core.IThat>> AtMostTwice(this aweXpect.Core.IThat> subject) { }
- public static aweXpect.Results.BetweenResult, aweXpect.Core.IThat>>, aweXpect.Core.Times> Between(this aweXpect.Core.IThat> subject, int minimum) { }
- public static aweXpect.Results.AndOrResult, aweXpect.Core.IThat>> Exactly(this aweXpect.Core.IThat> subject, aweXpect.Core.Times times) { }
+ public static aweXpect.Results.BetweenResult, aweXpect.Core.IThat>>, aweXpect.Core.Times> Between(this aweXpect.Core.IThat> subject, int minimum) { }
+ public static aweXpect.Results.AndOrWithinResult, aweXpect.Core.IThat>> Exactly(this aweXpect.Core.IThat> subject, aweXpect.Core.Times times) { }
public static aweXpect.Results.AndOrResult, aweXpect.Core.IThat>> Never(this aweXpect.Core.IThat> subject) { }
- public static aweXpect.Results.AndOrResult, aweXpect.Core.IThat>> Once(this aweXpect.Core.IThat> subject) { }
+ public static aweXpect.Results.AndOrWithinResult, aweXpect.Core.IThat>> Once(this aweXpect.Core.IThat