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
32 changes: 29 additions & 3 deletions Source/aweXpect.Core/Core/Sources/DelegateSource.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,34 @@
using System;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using aweXpect.Core.TimeSystem;

namespace aweXpect.Core.Sources;

internal class DelegateSource(Action<CancellationToken>? action) : IValueSource<DelegateValue>
internal class DelegateSource : IValueSource<DelegateValue>
{
private readonly Action<CancellationToken>? _action;

public DelegateSource(Action<CancellationToken>? action)
{
ThrowIfAsyncVoid(action?.GetMethodInfo(), "Func<CancellationToken, Task>");
_action = action;
}

public DelegateSource(Action? action)
{
ThrowIfAsyncVoid(action?.GetMethodInfo(), "Func<Task>");
_action = action is null ? null : _ => action();
}

#region IValueSource<DelegateValue> Members

public Task<DelegateValue> GetValue(ITimeSystem timeSystem,
CancellationToken cancellationToken)
{
if (action is null)
if (_action is null)
{
return Task.FromResult(new DelegateValue(null, TimeSpan.Zero, true));
}
Expand All @@ -21,7 +37,7 @@ public Task<DelegateValue> GetValue(ITimeSystem timeSystem,
try
{
sw.Start();
action(cancellationToken);
_action(cancellationToken);
sw.Stop();
return Task.FromResult(new DelegateValue(null, sw.Elapsed));
}
Expand All @@ -32,4 +48,14 @@ public Task<DelegateValue> GetValue(ITimeSystem timeSystem,
}

#endregion

private static void ThrowIfAsyncVoid(MethodInfo? method, string replaceType)
{
if (method is not null &&
Attribute.IsDefined(method, typeof(AsyncStateMachineAttribute), true))
{
throw new InvalidOperationException(
$"Cannot use aweXpect on an async void method: Use {replaceType} instead.");
}
}
}
2 changes: 1 addition & 1 deletion Source/aweXpect.Core/Expect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public static ThatDelegate.WithoutValue That(Action @delegate,
[CallerArgumentExpression("delegate")] string doNotPopulateThisValue = "")
=> new(new ExpectationBuilder<DelegateValue>(
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
new DelegateSource(@delegate is null ? null : _ => @delegate()), doNotPopulateThisValue));
new DelegateSource(@delegate), doNotPopulateThisValue));

/// <summary>
/// Specify expectations for the current <see cref="Action{CancellationToken}" /> <paramref name="delegate" />.
Expand Down
58 changes: 58 additions & 0 deletions Tests/aweXpect.Internal.Tests/ThatTests/DelegateTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,64 @@ namespace aweXpect.Internal.Tests.ThatTests;

public sealed class DelegateTests
{
[Fact]
public async Task ForAsyncVoidAction_WhenVerifyingDoesNotThrow_ShouldThrowInvalidOperationException()
{
Task incompleteTask = new TaskCompletionSource<bool>().Task;
// ReSharper disable once AsyncVoidLambda
Action @delegate = async () => await incompleteTask;

async Task Act()
=> await That(@delegate).DoesNotThrow();

await That(Act).Throws<InvalidOperationException>()
.WithMessage("Cannot use aweXpect on an async void method: Use Func<Task> instead.");
}

[Fact]
public async Task
ForAsyncVoidAction_WithCancellationToken_WhenVerifyingDoesNotThrow_ShouldThrowInvalidOperationException()
{
Task incompleteTask = new TaskCompletionSource<bool>().Task;
// ReSharper disable once AsyncVoidLambda
Action<CancellationToken> @delegate = async _ => await incompleteTask;

async Task Act()
=> await That(@delegate).DoesNotThrow();

await That(Act).Throws<InvalidOperationException>()
.WithMessage("Cannot use aweXpect on an async void method: Use Func<CancellationToken, Task> instead.");
}

[Fact]
public async Task ForAsyncVoidAction_WhenVerifyingDoesThrow_ShouldThrowInvalidOperationException()
{
Task incompleteTask = new TaskCompletionSource<bool>().Task;
// ReSharper disable once AsyncVoidLambda
Action @delegate = async () => await incompleteTask;

async Task Act()
=> await That(@delegate).ThrowsException();

await That(Act).Throws<InvalidOperationException>()
.WithMessage("Cannot use aweXpect on an async void method: Use Func<Task> instead.");
}

[Fact]
public async Task
ForAsyncVoidAction_WithCancellationToken_WhenVerifyingDoesThrow_ShouldThrowInvalidOperationException()
{
Task incompleteTask = new TaskCompletionSource<bool>().Task;
// ReSharper disable once AsyncVoidLambda
Action<CancellationToken> @delegate = async _ => await incompleteTask;

async Task Act()
=> await That(@delegate).ThrowsException();

await That(Act).Throws<InvalidOperationException>()
.WithMessage("Cannot use aweXpect on an async void method: Use Func<CancellationToken, Task> instead.");
}

Comment thread
vbreuss marked this conversation as resolved.
[Theory]
[AutoData]
public async Task ShouldReturnValue_FuncTaskValue(int value)
Expand Down