Skip to content

Commit cb9073d

Browse files
authored
feat: support Within in verification expectations (#73)
Adds opt-in time-bounded (“Within”) verification support for Mockolate `VerificationResult` expectations by introducing a new `AndOrWithinResult` wrapper + `WithinOptions`, updating relevant constraints to run async verification, and extending the test suite to cover background-invocation scenarios. ### Key Changes: - Introduces `AndOrWithinResult` + `WithinOptions` to enable `.Within(...)` and improved cancellation handling for verification expectations. - Updates `ThatVerificationResult` constraints (`Once`/`Twice`/`Exactly`/`AtLeast*`/`Times`/`Between`) to support async verification via `IAsyncVerificationResult`. - Expands/adjusts tests and API snapshots; adds `aweXpect.Chronology` for time helpers in tests.
1 parent acd16ee commit cb9073d

38 files changed

Lines changed: 1910 additions & 598 deletions

Directory.Packages.props

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
</PropertyGroup>
55
<ItemGroup>
66
<PackageVersion Include="aweXpect" Version="2.30.0" />
7-
<PackageVersion Include="aweXpect.Core" Version="2.27.0" />
8-
<PackageVersion Include="Mockolate" Version="1.4.1" />
7+
<PackageVersion Include="aweXpect.Core" Version="2.28.0" />
8+
<PackageVersion Include="aweXpect.Chronology" Version="1.0.0" />
9+
<PackageVersion Include="Mockolate" Version="1.5.4" />
910
</ItemGroup>
1011
<ItemGroup>
1112
<PackageVersion Include="Nullable" Version="1.3.1" />

README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,39 @@ await That(mock.VerifyMock.Invoked.MyMethod()).AtMost(4.Times()); // At most 4
3030
await That(mock.VerifyMock.Invoked.MyMethod()).Exactly(2.Times()); // Exactly 2 times
3131
```
3232

33+
#### Asynchronous verification
34+
35+
With `Within(TimeSpan timeout)`, you can check whether the expected number of calls occurred within a given time
36+
interval. This is useful for asynchronous or delayed invocations in the background.
37+
38+
```csharp
39+
var mock = Mock.Create<IMyService>();
40+
41+
// Start asynchronous calls, e.g., in a Task
42+
Task.Run(async () =>
43+
{
44+
await Task.Delay(500);
45+
mock.MyMethod();
46+
});
47+
48+
// Verifies that MyMethod was called at least once within 1 second
49+
await That(mock.VerifyMock.Invoked.MyMethod())
50+
.AtLeastOnce()
51+
.Within(TimeSpan.FromSeconds(1));
52+
```
53+
54+
Instead of a fixed time span, you can also provide a `CancellationToken` to limit how long the verification should wait
55+
for the expected interactions:
56+
57+
```csharp
58+
var token = new CancellationTokenSource(TimeSpan.FromSeconds(1)).Token;
59+
60+
// Verifies that MyMethod was called at least once within 1 second
61+
await That(mock.VerifyMock.Invoked.MyMethod())
62+
.AtLeastOnce()
63+
.WithCancellation(token);
64+
```
65+
3366
### Interaction order
3467

3568
Verify that methods were called in a specific sequence:
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System;
2+
using System.Threading;
3+
4+
namespace aweXpect.Options;
5+
6+
/// <summary>
7+
/// The options for a verification result which allows specifying a timeout and/or cancellation token for the
8+
/// verification.
9+
/// </summary>
10+
public class WithinOptions
11+
{
12+
/// <summary>
13+
/// The timeout that is applied to the verification.
14+
/// </summary>
15+
public TimeSpan? Timeout { get; set; }
16+
17+
/// <summary>
18+
/// The cancellation token that is used to cancel the verification.
19+
/// </summary>
20+
public CancellationToken? CancellationToken { get; set; }
21+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System;
2+
using System.Threading;
3+
using aweXpect.Core;
4+
using aweXpect.Options;
5+
6+
namespace aweXpect.Results;
7+
8+
/// <summary>
9+
/// The result of a verification result which allows specifying a timeout and/or cancellation token for the
10+
/// verification.
11+
/// </summary>
12+
/// <remarks>
13+
/// <seealso cref="AndOrResult{TCollection, TThat}" />
14+
/// </remarks>
15+
public class AndOrWithinResult<TType, TThat>(
16+
ExpectationBuilder expectationBuilder,
17+
TThat returnValue,
18+
WithinOptions options)
19+
: AndOrWithinResult<TType, TThat, AndOrWithinResult<TType, TThat>>(
20+
expectationBuilder,
21+
returnValue,
22+
options);
23+
24+
/// <inheritdoc cref="AndOrWithinResult{TType, TThat}" />
25+
public class AndOrWithinResult<TType, TThat, TSelf> : AndOrResult<TType, TThat>
26+
where TSelf : AndOrWithinResult<TType, TThat, TSelf>
27+
{
28+
private readonly WithinOptions _options;
29+
30+
/// <inheritdoc cref="AndOrWithinResult{TType, TThat, TSelf}" />
31+
protected AndOrWithinResult(ExpectationBuilder expectationBuilder, TThat returnValue, WithinOptions options)
32+
: base(expectationBuilder, returnValue)
33+
{
34+
_options = options;
35+
}
36+
37+
/// <summary>
38+
/// …within the given <paramref name="timeout" />.
39+
/// </summary>
40+
public TSelf Within(TimeSpan timeout)
41+
{
42+
_options.Timeout = timeout;
43+
return (TSelf)this;
44+
}
45+
46+
/// <summary>
47+
/// …with the given <paramref name="cancellationToken" />.
48+
/// </summary>
49+
public new TSelf WithCancellation(CancellationToken cancellationToken)
50+
{
51+
_options.CancellationToken = cancellationToken;
52+
base.WithCancellation(cancellationToken);
53+
return (TSelf)this;
54+
}
55+
}
Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using aweXpect.Core;
22
using aweXpect.Helpers;
3+
using aweXpect.Options;
34
using aweXpect.Results;
45
using Mockolate.Verify;
56

@@ -8,11 +9,16 @@ namespace aweXpect;
89
public static partial class ThatVerificationResult
910
{
1011
/// <summary>
11-
/// Verifies that the checked interaction happened at least the number of <paramref name="times"/>.
12+
/// Verifies that the checked interaction happened at least the number of <paramref name="times" />.
1213
/// </summary>
13-
public static AndOrResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>> AtLeast<TVerify>(
14-
this IThat<VerificationResult<TVerify>> subject, Times times)
15-
=> new(subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
16-
=> new HasAtLeastConstraint<TVerify>(expectationBuilder, it, grammars, times.Value)),
17-
subject);
14+
public static AndOrWithinResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>>
15+
AtLeast<TVerify>(this IThat<VerificationResult<TVerify>> subject, Times times)
16+
{
17+
WithinOptions options = new();
18+
return new AndOrWithinResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>>(
19+
subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
20+
=> new HasAtLeastConstraint<TVerify>(expectationBuilder, it, grammars, times.Value, options)),
21+
subject,
22+
options);
23+
}
1824
}
Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using aweXpect.Core;
22
using aweXpect.Helpers;
3+
using aweXpect.Options;
34
using aweXpect.Results;
45
using Mockolate.Verify;
56

@@ -10,9 +11,14 @@ public static partial class ThatVerificationResult
1011
/// <summary>
1112
/// Verifies that the checked interaction happened at least once.
1213
/// </summary>
13-
public static AndOrResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>> AtLeastOnce<TVerify>(
14-
this IThat<VerificationResult<TVerify>> subject)
15-
=> new(subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
16-
=> new HasAtLeastConstraint<TVerify>(expectationBuilder, it, grammars, 1)),
17-
subject);
14+
public static AndOrWithinResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>>
15+
AtLeastOnce<TVerify>(this IThat<VerificationResult<TVerify>> subject)
16+
{
17+
WithinOptions options = new();
18+
return new AndOrWithinResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>>(
19+
subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
20+
=> new HasAtLeastConstraint<TVerify>(expectationBuilder, it, grammars, 1, options)),
21+
subject,
22+
options);
23+
}
1824
}
Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using aweXpect.Core;
22
using aweXpect.Helpers;
3+
using aweXpect.Options;
34
using aweXpect.Results;
45
using Mockolate.Verify;
56

@@ -10,9 +11,14 @@ public static partial class ThatVerificationResult
1011
/// <summary>
1112
/// Verifies that the checked interaction happened at least twice.
1213
/// </summary>
13-
public static AndOrResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>> AtLeastTwice<TVerify>(
14-
this IThat<VerificationResult<TVerify>> subject)
15-
=> new(subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
16-
=> new HasAtLeastConstraint<TVerify>(expectationBuilder, it, grammars, 2)),
17-
subject);
14+
public static AndOrWithinResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>>
15+
AtLeastTwice<TVerify>(this IThat<VerificationResult<TVerify>> subject)
16+
{
17+
WithinOptions options = new();
18+
return new AndOrWithinResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>>(
19+
subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
20+
=> new HasAtLeastConstraint<TVerify>(expectationBuilder, it, grammars, 2, options)),
21+
subject,
22+
options);
23+
}
1824
}

Source/aweXpect.Mockolate/ThatVerificationResult.AtMost.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ namespace aweXpect;
88
public static partial class ThatVerificationResult
99
{
1010
/// <summary>
11-
/// Verifies that the checked interaction happened at most the number of <paramref name="times"/>.
11+
/// Verifies that the checked interaction happened at most the number of <paramref name="times" />.
1212
/// </summary>
13-
public static AndOrResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>> AtMost<TVerify>(
14-
this IThat<VerificationResult<TVerify>> subject, Times times)
13+
public static AndOrResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>>
14+
AtMost<TVerify>(this IThat<VerificationResult<TVerify>> subject, Times times)
1515
=> new(subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
1616
=> new HasAtMostConstraint<TVerify>(expectationBuilder, it, grammars, times.Value)),
1717
subject);

Source/aweXpect.Mockolate/ThatVerificationResult.AtMostOnce.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ public static partial class ThatVerificationResult
1010
/// <summary>
1111
/// Verifies that the checked interaction happened at most once.
1212
/// </summary>
13-
public static AndOrResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>> AtMostOnce<TVerify>(
14-
this IThat<VerificationResult<TVerify>> subject)
13+
public static AndOrResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>>
14+
AtMostOnce<TVerify>(this IThat<VerificationResult<TVerify>> subject)
1515
=> new(subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
1616
=> new HasAtMostConstraint<TVerify>(expectationBuilder, it, grammars, 1)),
1717
subject);

Source/aweXpect.Mockolate/ThatVerificationResult.AtMostTwice.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ public static partial class ThatVerificationResult
1010
/// <summary>
1111
/// Verifies that the checked interaction happened at most twice.
1212
/// </summary>
13-
public static AndOrResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>> AtMostTwice<TVerify>(
14-
this IThat<VerificationResult<TVerify>> subject)
13+
public static AndOrResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>>
14+
AtMostTwice<TVerify>(this IThat<VerificationResult<TVerify>> subject)
1515
=> new(subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
1616
=> new HasAtMostConstraint<TVerify>(expectationBuilder, it, grammars, 2)),
1717
subject);

0 commit comments

Comments
 (0)