diff --git a/Src/FluentAssertions/Specialized/AsyncFunctionAssertions.cs b/Src/FluentAssertions/Specialized/AsyncFunctionAssertions.cs
index c045ff9b9b..600e714327 100644
--- a/Src/FluentAssertions/Specialized/AsyncFunctionAssertions.cs
+++ b/Src/FluentAssertions/Specialized/AsyncFunctionAssertions.cs
@@ -174,6 +174,115 @@ private static void NotThrow(Exception exception, string because, object[] becau
}
}
+ ///
+ /// Asserts that the current stops throwing any exception
+ /// after a specified amount of time.
+ ///
+ ///
+ /// The is invoked. If it raises an exception,
+ /// the invocation is repeated until it either stops raising any exceptions
+ /// or the specified wait time is exceeded.
+ ///
+ ///
+ /// The time after which the should have stopped throwing any exception.
+ ///
+ ///
+ /// The time between subsequent invocations of the .
+ ///
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because, it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ /// Throws if waitTime or pollInterval are negative.
+ public void NotThrowAfter(TimeSpan waitTime, TimeSpan pollInterval, string because = "", params object[] becauseArgs)
+ {
+ if (waitTime < TimeSpan.Zero)
+ {
+ throw new ArgumentOutOfRangeException(nameof(waitTime), $"The value of {nameof(waitTime)} must be non-negative.");
+ }
+
+ if (pollInterval < TimeSpan.Zero)
+ {
+ throw new ArgumentOutOfRangeException(nameof(pollInterval), $"The value of {nameof(pollInterval)} must be non-negative.");
+ }
+
+
+ TimeSpan? invocationEndTime = null;
+ Exception exception = null;
+ var watch = Stopwatch.StartNew();
+
+ while (invocationEndTime is null || invocationEndTime < waitTime)
+ {
+ exception = InvokeSubjectWithInterception();
+ if (exception is null)
+ {
+ return;
+ }
+ Task.Delay(pollInterval).Wait();
+ invocationEndTime = watch.Elapsed;
+ }
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Did not expect any exceptions after {0}{reason}, but found {1}.", waitTime, exception);
+ }
+
+ ///
+ /// Asserts that the current stops throwing any exception
+ /// after a specified amount of time.
+ ///
+ ///
+ /// The is invoked. If it raises an exception,
+ /// the invocation is repeated until it either stops raising any exceptions
+ /// or the specified wait time is exceeded.
+ ///
+ ///
+ /// The time after which the should have stopped throwing any exception.
+ ///
+ ///
+ /// The time between subsequent invocations of the .
+ ///
+ ///
+ /// A formatted phrase as is supported by explaining why the assertion
+ /// is needed. If the phrase does not start with the word because, it is prepended automatically.
+ ///
+ ///
+ /// Zero or more objects to format using the placeholders in .
+ ///
+ /// Throws if waitTime or pollInterval are negative.
+ public async Task NotThrowAfterAsync(TimeSpan waitTime, TimeSpan pollInterval, string because = "", params object[] becauseArgs)
+ {
+ if (waitTime < TimeSpan.Zero)
+ {
+ throw new ArgumentOutOfRangeException(nameof(waitTime), $"The value of {nameof(waitTime)} must be non-negative.");
+ }
+
+ if (pollInterval < TimeSpan.Zero)
+ {
+ throw new ArgumentOutOfRangeException(nameof(pollInterval), $"The value of {nameof(pollInterval)} must be non-negative.");
+ }
+
+
+ TimeSpan? invocationEndTime = null;
+ Exception exception = null;
+ var watch = Stopwatch.StartNew();
+
+ while (invocationEndTime is null || invocationEndTime < waitTime)
+ {
+ exception = await InvokeSubjectWithInterceptionAsync();
+ if (exception is null)
+ {
+ return;
+ }
+ await Task.Delay(pollInterval);
+ invocationEndTime = watch.Elapsed;
+ }
+ Execute.Assertion
+ .BecauseOf(because, becauseArgs)
+ .FailWith("Did not expect any exceptions after {0}{reason}, but found {1}.", waitTime, exception);
+ }
private static Exception GetFirstNonAggregateException(Exception exception)
{
Exception nonAggregateException = exception;
diff --git a/Tests/Shared.Specs/AsyncFunctionExceptionAssertionSpecs.cs b/Tests/Shared.Specs/AsyncFunctionExceptionAssertionSpecs.cs
index 6320f73a08..c73ae00da1 100644
--- a/Tests/Shared.Specs/AsyncFunctionExceptionAssertionSpecs.cs
+++ b/Tests/Shared.Specs/AsyncFunctionExceptionAssertionSpecs.cs
@@ -1,5 +1,7 @@
using System;
+using System.Diagnostics;
using System.Threading.Tasks;
+using FluentAssertions.Extensions;
using Xunit;
using Xunit.Sdk;
@@ -26,7 +28,8 @@ public void When_subject_throws_subclass_of_expected_exact_exception_it_should_f
// Assert
//-----------------------------------------------------------------------------------------------------------
action.Should().Throw()
- .WithMessage("Expected type to be System.ArgumentException because IFoo.Do should do that, but found System.ArgumentNullException.");
+ .WithMessage(
+ "Expected type to be System.ArgumentException because IFoo.Do should do that, but found System.ArgumentNullException.");
}
[Fact]
@@ -83,7 +86,6 @@ public async void When_async_method_throws_async_expected_exception_it_should_su
// Assert
//-----------------------------------------------------------------------------------------------------------
await action.Should().ThrowAsync();
-
}
[Fact]
@@ -105,7 +107,8 @@ public void When_async_method_does_not_throw_expected_exception_it_should_fail()
// Assert
//-----------------------------------------------------------------------------------------------------------
action.Should().Throw()
- .WithMessage("Expected System.InvalidOperationException because IFoo.Do should do that, but no exception was thrown*");
+ .WithMessage(
+ "Expected System.InvalidOperationException because IFoo.Do should do that, but no exception was thrown*");
}
[Fact]
@@ -127,7 +130,8 @@ public void When_async_method_throws_unexpected_exception_it_should_fail()
// Assert
//-----------------------------------------------------------------------------------------------------------
action.Should().Throw()
- .WithMessage("Expected System.InvalidOperationException because IFoo.Do should do that, but found*System.ArgumentException*");
+ .WithMessage(
+ "Expected System.InvalidOperationException because IFoo.Do should do that, but found*System.ArgumentException*");
}
[Fact]
@@ -236,7 +240,8 @@ public async Task When_subject_throws_subclass_of_expected_async_exact_exception
//-----------------------------------------------------------------------------------------------------------
// Assert
//-----------------------------------------------------------------------------------------------------------
- (await testAction.Should().ThrowAsync()).WithMessage("*ArgumentException*ABCDE*ArgumentNullException*");
+ (await testAction.Should().ThrowAsync()).WithMessage(
+ "*ArgumentException*ABCDE*ArgumentNullException*");
}
[Fact]
@@ -540,6 +545,245 @@ public void When_asserting_async_void_method_should_not_throw_specific_exception
//-----------------------------------------------------------------------------------------------------------
action.Should().Throw("*async*void*");
}
+
+ #region NotThrowAfter
+
+ [Fact]
+ public void When_wait_time_is_negative_for_async_func_executed_with_wait_it_should_throw()
+ {
+ //-----------------------------------------------------------------------------------------------------------
+ // Arrange
+ //-----------------------------------------------------------------------------------------------------------
+ var waitTime = -1.Milliseconds();
+ var pollInterval = 10.Milliseconds();
+
+ var asyncObject = new AsyncClass();
+ Func someFunc = async () => await asyncObject.SucceedAsync();
+
+ //-----------------------------------------------------------------------------------------------------------
+ // Act
+ //-----------------------------------------------------------------------------------------------------------
+ Action act = () => someFunc.Should().NotThrowAfter(waitTime, pollInterval);
+
+ //-----------------------------------------------------------------------------------------------------------
+ // Assert
+ //-----------------------------------------------------------------------------------------------------------
+ act.Should().Throw()
+ .WithMessage("* value of waitTime must be non-negative*");
+ }
+
+ [Fact]
+ public void When_poll_interval_is_negative_for_async_func_executed_with_wait_it_should_throw()
+ {
+ //-----------------------------------------------------------------------------------------------------------
+ // Arrange
+ //-----------------------------------------------------------------------------------------------------------
+ var waitTime = 10.Milliseconds();
+ var pollInterval = -1.Milliseconds();
+
+ var asyncObject = new AsyncClass();
+ Func someFunc = async () => await asyncObject.SucceedAsync();
+
+ //-----------------------------------------------------------------------------------------------------------
+ // Act
+ //-----------------------------------------------------------------------------------------------------------
+ Action act = () => someFunc.Should().NotThrowAfter(waitTime, pollInterval);
+
+ //-----------------------------------------------------------------------------------------------------------
+ // Assert
+ //-----------------------------------------------------------------------------------------------------------
+ act.Should().Throw()
+ .WithMessage("* value of pollInterval must be non-negative*");
+ }
+
+ [Fact]
+ public void
+ When_no_exception_should_be_thrown_for_async_func_executed_with_wait_after_wait_time_but_it_was_it_should_throw()
+ {
+ //-----------------------------------------------------------------------------------------------------------
+ // Arrange
+ //-----------------------------------------------------------------------------------------------------------
+ var watch = Stopwatch.StartNew();
+ var waitTime = 100.Milliseconds();
+ var pollInterval = 10.Milliseconds();
+
+ Func throwLongerThanWaitTime = async () =>
+ {
+ if (watch.Elapsed <= waitTime + (waitTime.Milliseconds / 2).Milliseconds())
+ {
+ throw new ArgumentException("An exception was forced");
+ }
+
+ await Task.Delay(0);
+ };
+
+ //-----------------------------------------------------------------------------------------------------------
+ // Act
+ //-----------------------------------------------------------------------------------------------------------
+ Action action = () => throwLongerThanWaitTime.Should()
+ .NotThrowAfter(waitTime, pollInterval, "we passed valid arguments");
+
+ //-----------------------------------------------------------------------------------------------------------
+ // Assert
+ //-----------------------------------------------------------------------------------------------------------
+ action.Should().Throw()
+ .WithMessage("Did not expect any exceptions after 0.100s because we passed valid arguments*");
+ }
+
+ [Fact]
+ public void When_no_exception_should_be_thrown_for_async_func_executed_with_wait_after_wait_time_and_none_was_it_should_not_throw()
+ {
+ //-----------------------------------------------------------------------------------------------------------
+ // Arrange
+ //-----------------------------------------------------------------------------------------------------------
+ var watch = Stopwatch.StartNew();
+ var waitTime = 100.Milliseconds();
+ var pollInterval = 10.Milliseconds();
+
+ Func throwShorterThanWaitTime = async () =>
+ {
+ if (watch.Elapsed <= (waitTime.Milliseconds / 2).Milliseconds())
+ {
+ throw new ArgumentException("An exception was forced");
+ }
+ await Task.Delay(0);
+ };
+
+ //-----------------------------------------------------------------------------------------------------------
+ // Act
+ //-----------------------------------------------------------------------------------------------------------
+
+ Action act = () => throwShorterThanWaitTime.Should().NotThrowAfter(waitTime, pollInterval);
+
+ //-----------------------------------------------------------------------------------------------------------
+ // Assert
+ //-----------------------------------------------------------------------------------------------------------
+
+ act.Should().NotThrow();
+ }
+
+ #endregion
+
+ #region NotThrowAfterAsync
+ [Fact]
+ public void When_wait_time_is_negative_for_async_func_it_should_throw()
+ {
+ //-----------------------------------------------------------------------------------------------------------
+ // Arrange
+ //-----------------------------------------------------------------------------------------------------------
+ var waitTime = -1.Milliseconds();
+ var pollInterval = 10.Milliseconds();
+
+ var asyncObject = new AsyncClass();
+ Func someFunc = async () => await asyncObject.SucceedAsync();
+
+ //-----------------------------------------------------------------------------------------------------------
+ // Act
+ //-----------------------------------------------------------------------------------------------------------
+ Func act = async () =>
+ await someFunc.Should().NotThrowAfterAsync(waitTime, pollInterval);
+
+ //-----------------------------------------------------------------------------------------------------------
+ // Assert
+ //-----------------------------------------------------------------------------------------------------------
+ act.Should().Throw()
+ .WithMessage("* value of waitTime must be non-negative*");
+ }
+
+ [Fact]
+ public void When_poll_interval_is_negative_for_async_func_it_should_throw()
+ {
+ //-----------------------------------------------------------------------------------------------------------
+ // Arrange
+ //-----------------------------------------------------------------------------------------------------------
+ var waitTime = 10.Milliseconds();
+ var pollInterval = -1.Milliseconds();
+
+ var asyncObject = new AsyncClass();
+ Func someFunc = async () => await asyncObject.SucceedAsync();
+
+ //-----------------------------------------------------------------------------------------------------------
+ // Act
+ //-----------------------------------------------------------------------------------------------------------
+ Func act = async () =>
+ await someFunc.Should().NotThrowAfterAsync(waitTime, pollInterval);
+
+ //-----------------------------------------------------------------------------------------------------------
+ // Assert
+ //-----------------------------------------------------------------------------------------------------------
+ act.Should().Throw()
+ .WithMessage("* value of pollInterval must be non-negative*");
+ }
+
+ [Fact]
+ public void When_no_exception_should_be_thrown_for_async_func_after_wait_time_but_it_was_it_should_throw()
+ {
+ //-----------------------------------------------------------------------------------------------------------
+ // Arrange
+ //-----------------------------------------------------------------------------------------------------------
+ var watch = Stopwatch.StartNew();
+ var waitTime = 100.Milliseconds();
+ var pollInterval = 10.Milliseconds();
+
+ Func throwLongerThanWaitTime = async () =>
+ {
+ if (watch.Elapsed <= waitTime + (waitTime.Milliseconds / 2).Milliseconds())
+ {
+ throw new ArgumentException("An exception was forced");
+ }
+
+ await Task.Delay(0);
+ };
+
+ //-----------------------------------------------------------------------------------------------------------
+ // Act
+ //-----------------------------------------------------------------------------------------------------------
+ Func action = async () =>
+ await throwLongerThanWaitTime.Should()
+ .NotThrowAfterAsync(waitTime, pollInterval, "we passed valid arguments");
+
+ //-----------------------------------------------------------------------------------------------------------
+ // Assert
+ //-----------------------------------------------------------------------------------------------------------
+ action.Should().Throw()
+ .WithMessage("Did not expect any exceptions after 0.100s because we passed valid arguments*");
+ }
+
+ [Fact]
+ public void When_no_exception_should_be_thrown_for_async_func_after_wait_time_and_none_was_it_should_not_throw()
+ {
+ //-----------------------------------------------------------------------------------------------------------
+ // Arrange
+ //-----------------------------------------------------------------------------------------------------------
+ var watch = Stopwatch.StartNew();
+ var waitTime = 100.Milliseconds();
+ var pollInterval = 10.Milliseconds();
+
+ Func throwShorterThanWaitTime = async () =>
+ {
+ if (watch.Elapsed <= (waitTime.Milliseconds / 2).Milliseconds())
+ {
+ throw new ArgumentException("An exception was forced");
+ }
+
+ await Task.Delay(0);
+ };
+
+ //-----------------------------------------------------------------------------------------------------------
+ // Act
+ //-----------------------------------------------------------------------------------------------------------
+
+ Func act = async () =>
+ await throwShorterThanWaitTime.Should().NotThrowAfterAsync(waitTime, pollInterval);
+
+ //-----------------------------------------------------------------------------------------------------------
+ // Assert
+ //-----------------------------------------------------------------------------------------------------------
+
+ act.Should().NotThrow();
+ }
+
+ #endregion
}
internal class AsyncClass