Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="aweXpect" Version="2.30.0" />
<PackageVersion Include="aweXpect.Core" Version="2.27.0" />
<PackageVersion Include="Mockolate" Version="1.4.1" />
<PackageVersion Include="aweXpect.Core" Version="2.28.0" />
<PackageVersion Include="aweXpect.Chronology" Version="1.0.0" />
<PackageVersion Include="Mockolate" Version="1.5.4" />
</ItemGroup>
<ItemGroup>
<PackageVersion Include="Nullable" Version="1.3.1" />
Expand Down
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<IMyService>();

// 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:
Expand Down
21 changes: 21 additions & 0 deletions Source/aweXpect.Mockolate/Options/WithinOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using System.Threading;

namespace aweXpect.Options;

/// <summary>
/// The options for a verification result which allows specifying a timeout and/or cancellation token for the
/// verification.
/// </summary>
public class WithinOptions
{
/// <summary>
/// The timeout that is applied to the verification.
/// </summary>
public TimeSpan? Timeout { get; set; }

/// <summary>
/// The cancellation token that is used to cancel the verification.
/// </summary>
public CancellationToken? CancellationToken { get; set; }
}
55 changes: 55 additions & 0 deletions Source/aweXpect.Mockolate/Results/AndOrWithinResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System;
using System.Threading;
using aweXpect.Core;
using aweXpect.Options;

namespace aweXpect.Results;

/// <summary>
/// The result of a verification result which allows specifying a timeout and/or cancellation token for the
/// verification.
/// </summary>
/// <remarks>
/// <seealso cref="AndOrResult{TCollection, TThat}" />
/// </remarks>
public class AndOrWithinResult<TType, TThat>(
ExpectationBuilder expectationBuilder,
TThat returnValue,
WithinOptions options)
: AndOrWithinResult<TType, TThat, AndOrWithinResult<TType, TThat>>(
expectationBuilder,
returnValue,
options);

/// <inheritdoc cref="AndOrWithinResult{TType, TThat}" />
public class AndOrWithinResult<TType, TThat, TSelf> : AndOrResult<TType, TThat>
where TSelf : AndOrWithinResult<TType, TThat, TSelf>
{
private readonly WithinOptions _options;

/// <inheritdoc cref="AndOrWithinResult{TType, TThat, TSelf}" />
protected AndOrWithinResult(ExpectationBuilder expectationBuilder, TThat returnValue, WithinOptions options)
: base(expectationBuilder, returnValue)
{
_options = options;
}

/// <summary>
/// …within the given <paramref name="timeout" />.
/// </summary>
public TSelf Within(TimeSpan timeout)
{
_options.Timeout = timeout;
return (TSelf)this;
}

/// <summary>
/// …with the given <paramref name="cancellationToken" />.
/// </summary>
public new TSelf WithCancellation(CancellationToken cancellationToken)
{
_options.CancellationToken = cancellationToken;
base.WithCancellation(cancellationToken);
return (TSelf)this;
}
}
18 changes: 12 additions & 6 deletions Source/aweXpect.Mockolate/ThatVerificationResult.AtLeast.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using aweXpect.Core;
using aweXpect.Helpers;
using aweXpect.Options;
using aweXpect.Results;
using Mockolate.Verify;

Expand All @@ -8,11 +9,16 @@ namespace aweXpect;
public static partial class ThatVerificationResult
{
/// <summary>
/// Verifies that the checked interaction happened at least the number of <paramref name="times"/>.
/// Verifies that the checked interaction happened at least the number of <paramref name="times" />.
/// </summary>
public static AndOrResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>> AtLeast<TVerify>(
this IThat<VerificationResult<TVerify>> subject, Times times)
=> new(subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
=> new HasAtLeastConstraint<TVerify>(expectationBuilder, it, grammars, times.Value)),
subject);
public static AndOrWithinResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>>
AtLeast<TVerify>(this IThat<VerificationResult<TVerify>> subject, Times times)
{
WithinOptions options = new();
return new AndOrWithinResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>>(
subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
=> new HasAtLeastConstraint<TVerify>(expectationBuilder, it, grammars, times.Value, options)),
subject,
options);
}
}
16 changes: 11 additions & 5 deletions Source/aweXpect.Mockolate/ThatVerificationResult.AtLeastOnce.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using aweXpect.Core;
using aweXpect.Helpers;
using aweXpect.Options;
using aweXpect.Results;
using Mockolate.Verify;

Expand All @@ -10,9 +11,14 @@ public static partial class ThatVerificationResult
/// <summary>
/// Verifies that the checked interaction happened at least once.
/// </summary>
public static AndOrResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>> AtLeastOnce<TVerify>(
this IThat<VerificationResult<TVerify>> subject)
=> new(subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
=> new HasAtLeastConstraint<TVerify>(expectationBuilder, it, grammars, 1)),
subject);
public static AndOrWithinResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>>
AtLeastOnce<TVerify>(this IThat<VerificationResult<TVerify>> subject)
{
WithinOptions options = new();
return new AndOrWithinResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>>(
subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
=> new HasAtLeastConstraint<TVerify>(expectationBuilder, it, grammars, 1, options)),
subject,
options);
}
}
16 changes: 11 additions & 5 deletions Source/aweXpect.Mockolate/ThatVerificationResult.AtLeastTwice.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using aweXpect.Core;
using aweXpect.Helpers;
using aweXpect.Options;
using aweXpect.Results;
using Mockolate.Verify;

Expand All @@ -10,9 +11,14 @@ public static partial class ThatVerificationResult
/// <summary>
/// Verifies that the checked interaction happened at least twice.
/// </summary>
public static AndOrResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>> AtLeastTwice<TVerify>(
this IThat<VerificationResult<TVerify>> subject)
=> new(subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
=> new HasAtLeastConstraint<TVerify>(expectationBuilder, it, grammars, 2)),
subject);
public static AndOrWithinResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>>
AtLeastTwice<TVerify>(this IThat<VerificationResult<TVerify>> subject)
{
WithinOptions options = new();
return new AndOrWithinResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>>(
subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
=> new HasAtLeastConstraint<TVerify>(expectationBuilder, it, grammars, 2, options)),
subject,
options);
}
}
6 changes: 3 additions & 3 deletions Source/aweXpect.Mockolate/ThatVerificationResult.AtMost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ namespace aweXpect;
public static partial class ThatVerificationResult
{
/// <summary>
/// Verifies that the checked interaction happened at most the number of <paramref name="times"/>.
/// Verifies that the checked interaction happened at most the number of <paramref name="times" />.
/// </summary>
public static AndOrResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>> AtMost<TVerify>(
this IThat<VerificationResult<TVerify>> subject, Times times)
public static AndOrResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>>
AtMost<TVerify>(this IThat<VerificationResult<TVerify>> subject, Times times)
=> new(subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
=> new HasAtMostConstraint<TVerify>(expectationBuilder, it, grammars, times.Value)),
subject);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ public static partial class ThatVerificationResult
/// <summary>
/// Verifies that the checked interaction happened at most once.
/// </summary>
public static AndOrResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>> AtMostOnce<TVerify>(
this IThat<VerificationResult<TVerify>> subject)
public static AndOrResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>>
AtMostOnce<TVerify>(this IThat<VerificationResult<TVerify>> subject)
=> new(subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
=> new HasAtMostConstraint<TVerify>(expectationBuilder, it, grammars, 1)),
subject);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ public static partial class ThatVerificationResult
/// <summary>
/// Verifies that the checked interaction happened at most twice.
/// </summary>
public static AndOrResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>> AtMostTwice<TVerify>(
this IThat<VerificationResult<TVerify>> subject)
public static AndOrResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>>
AtMostTwice<TVerify>(this IThat<VerificationResult<TVerify>> subject)
=> new(subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
=> new HasAtMostConstraint<TVerify>(expectationBuilder, it, grammars, 2)),
subject);
Expand Down
66 changes: 55 additions & 11 deletions Source/aweXpect.Mockolate/ThatVerificationResult.Between.cs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -13,28 +16,69 @@ public static partial class ThatVerificationResult
/// <summary>
/// Verifies that the checked interaction happened between <paramref name="minimum" />…
/// </summary>
public static BetweenResult<AndOrResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>>, Times>
Between<TVerify>(
this IThat<VerificationResult<TVerify>> subject, int minimum)
=> new(maximum => new AndOrResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>>(subject.Get()
.ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
=> new HasBetweenConstraint<TVerify>(expectationBuilder, it, grammars, minimum, maximum.Value)),
subject));

public static BetweenResult<AndOrWithinResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>>,
Times>
Between<TVerify>(this IThat<VerificationResult<TVerify>> subject, int minimum)
{
WithinOptions options = new();
return new
BetweenResult<AndOrWithinResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>>,
Times>(maximum
=> new AndOrWithinResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>>(
subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
=> new HasBetweenConstraint<TVerify>(expectationBuilder, it, grammars, minimum,
maximum.Value, options)),
subject,
options));
}

private sealed class HasBetweenConstraint<TVerify>(
ExpectationBuilder expectationBuilder,
string it,
ExpectationGrammars grammars,
int minimum,
int maximum)
int maximum,
WithinOptions options)
: ConstraintResult.WithValue<VerificationResult<TVerify>>(grammars),
IValueConstraint<VerificationResult<TVerify>>
IAsyncConstraint<VerificationResult<TVerify>>
{
private int _count = -1;
private string _expectation = "";

public ConstraintResult IsMetBy(VerificationResult<TVerify> actual)
public async Task<ConstraintResult> IsMetBy(VerificationResult<TVerify> 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;
Expand Down
18 changes: 12 additions & 6 deletions Source/aweXpect.Mockolate/ThatVerificationResult.Exactly.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using aweXpect.Core;
using aweXpect.Helpers;
using aweXpect.Options;
using aweXpect.Results;
using Mockolate.Verify;

Expand All @@ -8,11 +9,16 @@ namespace aweXpect;
public static partial class ThatVerificationResult
{
/// <summary>
/// Verifies that the checked interaction happened exactly the number of <paramref name="times"/>.
/// Verifies that the checked interaction happened exactly the number of <paramref name="times" />.
/// </summary>
public static AndOrResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>> Exactly<TVerify>(
this IThat<VerificationResult<TVerify>> subject, Times times)
=> new(subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
=> new HasExactlyConstraint<TVerify>(expectationBuilder, it, grammars, times.Value)),
subject);
public static AndOrWithinResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>>
Exactly<TVerify>(this IThat<VerificationResult<TVerify>> subject, Times times)
{
WithinOptions options = new();
return new AndOrWithinResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>>(
subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
=> new HasExactlyConstraint<TVerify>(expectationBuilder, it, grammars, times.Value, options)),
subject,
options);
}
}
13 changes: 9 additions & 4 deletions Source/aweXpect.Mockolate/ThatVerificationResult.Never.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using aweXpect.Core;
using aweXpect.Helpers;
using aweXpect.Options;
using aweXpect.Results;
using Mockolate.Verify;

Expand All @@ -10,9 +11,13 @@ public static partial class ThatVerificationResult
/// <summary>
/// Verifies that the checked interaction happened never.
/// </summary>
public static AndOrResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>> Never<TVerify>(
this IThat<VerificationResult<TVerify>> subject)
=> new(subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
=> new HasExactlyConstraint<TVerify>(expectationBuilder, it, grammars, 0)),
public static AndOrResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>>
Never<TVerify>(this IThat<VerificationResult<TVerify>> subject)
{
WithinOptions options = new();
return new AndOrResult<VerificationResult<TVerify>, IThat<VerificationResult<TVerify>>>(
subject.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars)
=> new HasExactlyConstraint<TVerify>(expectationBuilder, it, grammars, 0, options)),
subject);
}
}
Loading
Loading