Skip to content

Commit

Permalink
Add NotThrowAfter for Func<Task>
Browse files Browse the repository at this point in the history
  • Loading branch information
frederik-h committed Dec 20, 2018
1 parent f1525e5 commit f22fcdb
Show file tree
Hide file tree
Showing 3 changed files with 353 additions and 3 deletions.
109 changes: 109 additions & 0 deletions Src/FluentAssertions/Specialized/AsyncFunctionAssertions.cs
Expand Up @@ -174,6 +174,115 @@ private static void NotThrow(Exception exception, string because, object[] becau
}
}

/// <summary>
/// Asserts that the current <see cref="Func{T}"/> stops throwing any exception
/// after a specified amount of time.
/// </summary>
/// <remarks>
/// The <see cref="Func{T}"/> 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.
/// </remarks>
/// <param name="waitTime">
/// The time after which the <see cref="Func{T}"/> should have stopped throwing any exception.
/// </param>
/// <param name="pollInterval">
/// The time between subsequent invocations of the <see cref="Func{T}"/>.
/// </param>
/// <param name="because">
/// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
/// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically.
/// </param>
/// <param name="becauseArgs">
/// Zero or more objects to format using the placeholders in <see cref="because" />.
/// </param>
/// <exception cref="ArgumentOutOfRangeException">Throws if waitTime or pollInterval are negative.</exception>
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);
}

/// <summary>
/// Asserts that the current <see cref="Func{T}"/> stops throwing any exception
/// after a specified amount of time.
/// </summary>
/// <remarks>
/// The <see cref="Func{T}"/> 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.
/// </remarks>
/// <param name="waitTime">
/// The time after which the <see cref="Func{T}"/> should have stopped throwing any exception.
/// </param>
/// <param name="pollInterval">
/// The time between subsequent invocations of the <see cref="Func{T}"/>.
/// </param>
/// <param name="because">
/// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
/// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically.
/// </param>
/// <param name="becauseArgs">
/// Zero or more objects to format using the placeholders in <see cref="because" />.
/// </param>
/// <exception cref="ArgumentOutOfRangeException">Throws if waitTime or pollInterval are negative.</exception>
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;
Expand Down
2 changes: 1 addition & 1 deletion Src/FluentAssertions/Specialized/FunctionAssertions.cs
Expand Up @@ -138,7 +138,7 @@ private static void NotThrow(Exception exception, string because, object[] becau
/// after a specified amount of time.
/// </summary>
/// <remarks>
/// The <see cref="Action"/> is invoked. If it raises an exception,
/// The <see cref="Func{T}"/> 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.
/// </remarks>
Expand Down
245 changes: 243 additions & 2 deletions 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;

Expand Down Expand Up @@ -83,7 +85,6 @@ public async void When_async_method_throws_async_expected_exception_it_should_su
// Assert
//-----------------------------------------------------------------------------------------------------------
await action.Should().ThrowAsync<ArgumentException>();

}

[Fact]
Expand Down Expand Up @@ -236,7 +237,8 @@ public async Task When_subject_throws_subclass_of_expected_async_exact_exception
//-----------------------------------------------------------------------------------------------------------
// Assert
//-----------------------------------------------------------------------------------------------------------
(await testAction.Should().ThrowAsync<XunitException>()).WithMessage("*ArgumentException*ABCDE*ArgumentNullException*");
(await testAction.Should().ThrowAsync<XunitException>())
.WithMessage("*ArgumentException*ABCDE*ArgumentNullException*");
}

[Fact]
Expand Down Expand Up @@ -540,6 +542,245 @@ public void When_asserting_async_void_method_should_not_throw_specific_exception
//-----------------------------------------------------------------------------------------------------------
action.Should().Throw<InvalidOperationException>("*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<Task> someFunc = async () => await asyncObject.SucceedAsync();

//-----------------------------------------------------------------------------------------------------------
// Act
//-----------------------------------------------------------------------------------------------------------
Action act = () => someFunc.Should().NotThrowAfter(waitTime, pollInterval);

//-----------------------------------------------------------------------------------------------------------
// Assert
//-----------------------------------------------------------------------------------------------------------
act.Should().Throw<ArgumentOutOfRangeException>()
.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<Task> someFunc = async () => await asyncObject.SucceedAsync();

//-----------------------------------------------------------------------------------------------------------
// Act
//-----------------------------------------------------------------------------------------------------------
Action act = () => someFunc.Should().NotThrowAfter(waitTime, pollInterval);

//-----------------------------------------------------------------------------------------------------------
// Assert
//-----------------------------------------------------------------------------------------------------------
act.Should().Throw<ArgumentOutOfRangeException>()
.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<Task> 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<XunitException>()
.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<Task> 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<Task> someFunc = async () => await asyncObject.SucceedAsync();

//-----------------------------------------------------------------------------------------------------------
// Act
//-----------------------------------------------------------------------------------------------------------
Func<Task> act = async () =>
await someFunc.Should().NotThrowAfterAsync(waitTime, pollInterval);

//-----------------------------------------------------------------------------------------------------------
// Assert
//-----------------------------------------------------------------------------------------------------------
act.Should().Throw<ArgumentOutOfRangeException>()
.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<Task> someFunc = async () => await asyncObject.SucceedAsync();

//-----------------------------------------------------------------------------------------------------------
// Act
//-----------------------------------------------------------------------------------------------------------
Func<Task> act = async () =>
await someFunc.Should().NotThrowAfterAsync(waitTime, pollInterval);

//-----------------------------------------------------------------------------------------------------------
// Assert
//-----------------------------------------------------------------------------------------------------------
act.Should().Throw<ArgumentOutOfRangeException>()
.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<Task> throwLongerThanWaitTime = async () =>
{
if (watch.Elapsed <= waitTime + (waitTime.Milliseconds / 2).Milliseconds())
{
throw new ArgumentException("An exception was forced");
}
await Task.Delay(0);
};

//-----------------------------------------------------------------------------------------------------------
// Act
//-----------------------------------------------------------------------------------------------------------
Func<Task> action = async () =>
await throwLongerThanWaitTime.Should()
.NotThrowAfterAsync(waitTime, pollInterval, "we passed valid arguments");

//-----------------------------------------------------------------------------------------------------------
// Assert
//-----------------------------------------------------------------------------------------------------------
action.Should().Throw<XunitException>()
.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<Task> throwShorterThanWaitTime = async () =>
{
if (watch.Elapsed <= (waitTime.Milliseconds / 2).Milliseconds())
{
throw new ArgumentException("An exception was forced");
}
await Task.Delay(0);
};

//-----------------------------------------------------------------------------------------------------------
// Act
//-----------------------------------------------------------------------------------------------------------

Func<Task> act = async () =>
await throwShorterThanWaitTime.Should().NotThrowAfterAsync(waitTime, pollInterval);

//-----------------------------------------------------------------------------------------------------------
// Assert
//-----------------------------------------------------------------------------------------------------------

act.Should().NotThrow();
}

#endregion
}

internal class AsyncClass
Expand Down

0 comments on commit f22fcdb

Please sign in to comment.