Skip to content

Commit

Permalink
Merge pull request #4322 from uecasm/that-async
Browse files Browse the repository at this point in the history
ThatAsync and MultipleAsync
  • Loading branch information
OsirisTerje committed Apr 6, 2023
2 parents 1ae42fe + d0c5a9c commit efb6392
Show file tree
Hide file tree
Showing 4 changed files with 249 additions and 0 deletions.
84 changes: 84 additions & 0 deletions src/NUnitFramework/framework/Assert.ThatAsync.cs
@@ -0,0 +1,84 @@
// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt

#nullable enable

using System.Runtime.ExceptionServices;
using System.Threading.Tasks;
using System;
using NUnit.Framework.Constraints;

namespace NUnit.Framework
{
public abstract partial class Assert
{
#region Assert.ThatAsync

/// <summary>
/// Apply a constraint to an async delegate. Returns without throwing an exception when inside a multiple assert block.
/// </summary>
/// <param name="code">An AsyncTestDelegate to be executed</param>
/// <param name="constraint">A Constraint expression to be applied</param>
/// <returns>Awaitable.</returns>
public static Task ThatAsync(AsyncTestDelegate code, IResolveConstraint constraint)
{
return ThatAsync(code, constraint, null, null);
}

/// <summary>
/// Apply a constraint to an async delegate. Returns without throwing an exception when inside a multiple assert block.
/// </summary>
/// <param name="code">An AsyncTestDelegate to be executed</param>
/// <param name="constraint">A Constraint expression to be applied</param>
/// <param name="message">The message that will be displayed on failure</param>
/// <param name="args">Arguments to be used in formatting the message</param>
/// <returns>Awaitable.</returns>
public static async Task ThatAsync(AsyncTestDelegate code, IResolveConstraint constraint, string? message, params object?[]? args)
{
try
{
await code();
Assert.That(() => { }, constraint, message, args);
}
catch (Exception ex)
{
var edi = ExceptionDispatchInfo.Capture(ex);
Assert.That(() => edi.Throw(), constraint, message, args);
}
}

/// <summary>
/// Apply a constraint to an async delegate. Returns without throwing an exception when inside a multiple assert block.
/// </summary>
/// <param name="code">An async method to be executed</param>
/// <param name="constraint">A Constraint expression to be applied</param>
/// <returns>Awaitable.</returns>
public static Task ThatAsync<T>(Func<Task<T>> code, IResolveConstraint constraint)
{
return ThatAsync(code, constraint, null, null);
}

/// <summary>
/// Apply a constraint to an async delegate. Returns without throwing an exception when inside a multiple assert block.
/// </summary>
/// <param name="code">An async method to be executed</param>
/// <param name="constraint">A Constraint expression to be applied</param>
/// <param name="message">The message that will be displayed on failure</param>
/// <param name="args">Arguments to be used in formatting the message</param>
/// <returns>Awaitable.</returns>
public static async Task ThatAsync<T>(Func<Task<T>> code, IResolveConstraint constraint, string? message, params object?[]? args)
{
try
{
var result = await code();
Assert.That(() => result, constraint, message, args);
}
catch (Exception ex)
{
var edi = ExceptionDispatchInfo.Capture(ex);
Assert.That(() => edi.Throw(), constraint, message, args);
}
}

#endregion
}
}
30 changes: 30 additions & 0 deletions src/NUnitFramework/framework/Assert.cs
Expand Up @@ -6,6 +6,7 @@
using System.Collections;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using NUnit.Framework.Constraints;
using NUnit.Framework.Interfaces;
using NUnit.Framework.Internal;
Expand Down Expand Up @@ -380,6 +381,35 @@ public static void Multiple(AsyncTestDelegate testDelegate)
}
}

/// <summary>
/// Wraps code containing a series of assertions, which should all
/// be executed, even if they fail. Failed results are saved and
/// reported at the end of the code block.
/// </summary>
/// <param name="testDelegate">An AsyncTestDelegate to be executed in Multiple Assertion mode.</param>
public static async Task MultipleAsync(AsyncTestDelegate testDelegate)
{
TestExecutionContext context = TestExecutionContext.CurrentContext;
Guard.OperationValid(context != null, "There is no current test execution context.");

context.MultipleAssertLevel++;

try
{
await testDelegate();
}
finally
{
context.MultipleAssertLevel--;
}

if (context.MultipleAssertLevel == 0 && context.CurrentResult.PendingFailures > 0)
{
context.CurrentResult.RecordTestCompletion();
throw new MultipleAssertException(context.CurrentResult);
}
}

#endregion

#region Helper Methods
Expand Down
12 changes: 12 additions & 0 deletions src/NUnitFramework/tests/Assertions/AssertMultipleTests.cs
@@ -1,6 +1,7 @@
// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt

using System;
using System.Threading.Tasks;
using NUnit.Framework.Interfaces;
using NUnit.TestData.AssertMultipleData;
using NUnit.TestUtilities;
Expand Down Expand Up @@ -74,6 +75,17 @@ public void AssertMultiple_InvalidAssertThrowsException(string methodName, strin
Assert.That(result.Message, Contains.Substring($"{invalidAssert} may not be used in a multiple assertion block."));
}

[Test]
public async Task AssertMultipleAsyncSucceeds()
{
await Assert.MultipleAsync(async () =>
{
await Assert.ThatAsync(() => Task.FromResult(42), Is.EqualTo(42));
Assert.That("hello", Is.EqualTo("hello"));
await Assert.ThatAsync(() => Task.FromException(new ArgumentNullException()), Throws.ArgumentNullException);
});
}

private ITestResult CheckResult(string methodName, ResultState expectedResultState, int expectedAsserts, params string[] assertionMessageRegex)
{
ITestResult result = TestBuilder.RunTestCase(typeof(AssertMultipleFixture), methodName);
Expand Down
123 changes: 123 additions & 0 deletions src/NUnitFramework/tests/Assertions/AssertThatAsyncTests.cs
@@ -0,0 +1,123 @@
// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt

using System;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework.Internal;

namespace NUnit.Framework.Assertions
{
[TestFixture]
public class AssertThatAsyncTests
{
[Test]
public async Task AssertionPasses_CompletedTask_ThrowsNothing()
{
await Assert.ThatAsync(() => Task.CompletedTask, Throws.Nothing);
}

[Test]
public async Task AssertionPasses_CanceledTask_ThrowsCanceled()
{
var cancel = new CancellationTokenSource();
cancel.Cancel();

await Assert.ThatAsync(() => Task.FromCanceled(cancel.Token),
Throws.InstanceOf<TaskCanceledException>()
.With.Property(nameof(TaskCanceledException.CancellationToken)).EqualTo(cancel.Token));
}

[Test]
public async Task AssertionPasses_FaultedTask_ThrowsMatchingException()
{
await Assert.ThatAsync(() => Task.FromException(new InvalidOperationException()), Throws.InvalidOperationException);
}

[Test]
public async Task AssertionPasses_CompletedTask_ThrowsNothingWithMessage()
{
await Assert.ThatAsync(() => Task.CompletedTask, Throws.Nothing, "Success");
}

[Test]
public async Task AssertionPasses_CanceledTask_ThrowsCanceledWithMessage()
{
var cancel = new CancellationTokenSource();
cancel.Cancel();

await Assert.ThatAsync(() => Task.FromCanceled(cancel.Token),
Throws.InstanceOf<TaskCanceledException>()
.With.Property(nameof(TaskCanceledException.CancellationToken)).EqualTo(cancel.Token),
"Cancelled");
}

[Test]
public async Task AssertionPasses_FaultedTask_ThrowsMatchingExceptionWithMessage()
{
await Assert.ThatAsync(() => Task.FromException(new InvalidOperationException()), Throws.InvalidOperationException, "Faulted");
}

[Test]
public async Task Failure_CompletedTask_ThrowsException()
{
await AssertAssertionFailsAsync(async () => await Assert.ThatAsync(() => Task.CompletedTask, Throws.InvalidOperationException));
}

[Test]
public async Task Failure_CanceledTask_ThrowsNothing()
{
var cancel = new CancellationTokenSource();
cancel.Cancel();

await AssertAssertionFailsAsync(async () => await Assert.ThatAsync(() => Task.FromCanceled(cancel.Token), Throws.Nothing));
}

[Test]
public async Task Failure_FaultedTask_ThrowsNothing()
{
await AssertAssertionFailsAsync(async () => await Assert.ThatAsync(() => Task.FromException(new InvalidOperationException()), Throws.Nothing));
}

[Test]
public async Task AssertionPasses_CompletedTaskWithResult_ThrowsNothing()
{
await Assert.ThatAsync(() => Task.FromResult(42), Throws.Nothing);
}

[Test]
public async Task AssertionPasses_CompletedTaskWithResult_EqualsResult()
{
await Assert.ThatAsync(() => Task.FromResult(42), Is.EqualTo(42));
}

[Test]
public async Task AssertionPasses_CanceledTaskWithResult_ThrowsCanceled()
{
var cancel = new CancellationTokenSource();
cancel.Cancel();

await Assert.ThatAsync(() => Task.FromCanceled<int>(cancel.Token),
Throws.InstanceOf<TaskCanceledException>()
.With.Property(nameof(TaskCanceledException.CancellationToken)).EqualTo(cancel.Token));
}

[Test]
public async Task AssertionPasses_FaultedTaskWithResult_ThrowsMatchingException()
{
await Assert.ThatAsync(() => Task.FromException<int>(new InvalidOperationException()), Throws.InvalidOperationException);
}

private static async Task AssertAssertionFailsAsync(Func<Task> assertion)
{
await Assert.ThatAsync(
async () =>
{
using (new TestExecutionContext.IsolatedContext())
{
await assertion();
}
},
Throws.InstanceOf<AssertionException>());
}
}
}

0 comments on commit efb6392

Please sign in to comment.