Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Should().NotThrowAfter assertion for actions #942

Merged
merged 23 commits into from Jan 11, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
0e8d5f8
Add wait time to NotThrow
frederik-h Sep 18, 2018
b7a41f8
Rename NotThrow overload with wait time
frederik-h Oct 8, 2018
2da5e3b
Changes requested in review of NotThrowAfter
frederik-h Oct 12, 2018
d83dd87
Minor variable naming and exception message change
frederik-h Oct 13, 2018
b46d678
Use portable method for sleeping in NotThrowAfter
frederik-h Oct 15, 2018
b6a22e1
Fix test name
frederik-h Nov 9, 2018
5d80597
Fix exception message in NotThrowAfter
frederik-h Nov 9, 2018
cdd339e
Revert "Use portable method for sleeping in NotThrowAfter"
frederik-h Nov 19, 2018
fe90171
Disable NotThrowAfter for netstandard < 2.0
frederik-h Nov 19, 2018
a8bdaf9
Merge remote-tracking branch 'upstream/master' into NotThrow-Wait
frederik-h Nov 19, 2018
a9aa045
Minor coding style changes
frederik-h Nov 22, 2018
cc58f1d
Remove code literal names from test names
frederik-h Nov 22, 2018
670d278
Minor code formatting fixes
frederik-h Nov 22, 2018
f1525e5
Add NotThrowAfter to FunctionAssertions
frederik-h Dec 7, 2018
f22fcdb
Add NotThrowAfter for Func<Task>
frederik-h Dec 20, 2018
bb1e1a0
Wrap assertion code of NotThrowAfter in local function
frederik-h Dec 30, 2018
36a850c
Increase wait times in NotThrowAfter tests
frederik-h Jan 1, 2019
e3004c2
Change way NowThrowAfterAsync calls the assertion task
frederik-h Jan 1, 2019
cfc76ca
Add documentation for NotThrowAfter
frederik-h Jan 5, 2019
40258c0
Increase wait time in NotThrowAfter-tests
frederik-h Jan 6, 2019
7da866c
Fixup whitespace
jnyrup Jan 7, 2019
ccfeeca
Merge pull request #1 from jnyrup/NotBeInDescendingOrder
frederik-h Jan 8, 2019
49ec86f
Change NotThrowAfter for Func<T> to return result
frederik-h Jan 9, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
50 changes: 49 additions & 1 deletion Src/FluentAssertions/Specialized/ActionAssertions.cs
Expand Up @@ -4,7 +4,7 @@
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;

using System.Threading.Tasks;
using FluentAssertions.Common;
using FluentAssertions.Execution;
using FluentAssertions.Primitives;
Expand Down Expand Up @@ -114,6 +114,54 @@ public void NotThrow(string because = "", params object[] becauseArgs)
}
}

/// <summary>
/// Asserts that the current <see cref="Action"/> stops throwing any exception
/// after a specified amount of time.
/// </summary>
/// <remarks>
/// The <see cref="Action"/> 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 in milliseconds after which the <see cref="Action"/> should have stopped throwing any exception.
frederik-h marked this conversation as resolved.
Show resolved Hide resolved
/// </param>
/// <param name="pollInterval">
/// The time between subsequent invocations of the <see cref="Action"/>.
/// </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(int waitTime, int pollInterval, string because = "", params object[] becauseArgs)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔧 I would prefer to use TimeSpan for waitTime and pollInterval.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔧 Maybe NotThrowAnymoreAfter

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 Do we also need to be able to specify an exception type here?
🤔 Do we also need this assertion for Func<Task> for consistency with the other exception APIs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe NotThrowAnymoreAfter

Are you sure? I think that this would be a tad too long. It might also be misleading. Doesn't "anymore" suggest that the Action must have been throwing exceptions at some point

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer to use TimeSpan for waitTime and pollInterval.

Sounds good. Would you keep an overload with the current signature or would you expect the user to convert from that representation to the TimeSpan?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True. Very true. Alright. Let's stick with NotThrowAfter

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you keep an overload with the current signature or would you expect the user to convert from that representation to the TimeSpan?

They can use the TimeSpan extension methods like 12.Seconds().

Copy link
Contributor Author

@frederik-h frederik-h Oct 11, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we also need to be able to specify an exception type here?

I have not encountered a use case for this so far. This would mean that the Action could be still throwing exceptions but not the specified one ... :confused. We could add it for consistency's sake. You could also wait until someone requests it. I don't know your policy regarding these kind of questions 😏

Do we also need this assertion for Func<Task> for consistency with the other exception APIs?

Probably.

{
FailIfSubjectIsAsyncVoid();
jnyrup marked this conversation as resolved.
Show resolved Hide resolved

if(waitTime < 0 || pollInterval < 0)
frederik-h marked this conversation as resolved.
Show resolved Hide resolved
frederik-h marked this conversation as resolved.
Show resolved Hide resolved
throw new ArgumentOutOfRangeException("The values of waitTime and pollInterval must be positive.");

var watch = Stopwatch.StartNew();
frederik-h marked this conversation as resolved.
Show resolved Hide resolved
long invocationEndTime = -1;
Exception exception = null;

while (invocationEndTime < waitTime)
{
exception = InvokeSubjectWithInterception();
if (exception is null)
frederik-h marked this conversation as resolved.
Show resolved Hide resolved
return;

Task.Delay(pollInterval).Wait();
frederik-h marked this conversation as resolved.
Show resolved Hide resolved
invocationEndTime = watch.ElapsedMilliseconds;
}
Execute.Assertion
.BecauseOf(because, becauseArgs)
.FailWith("Did not expect any exception after {0} milliseconds{reason}, but found {1}.", waitTime, exception);
frederik-h marked this conversation as resolved.
Show resolved Hide resolved
}

private Exception InvokeSubjectWithInterception()
{
Exception actualException = null;
Expand Down
59 changes: 57 additions & 2 deletions Tests/Shared.Specs/ExceptionAssertionSpecs.cs
@@ -1,10 +1,9 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Xunit;
using Xunit.Sdk;

using FakeItEasy;

using FluentAssertions.Primitives;

namespace FluentAssertions.Specs
Expand Down Expand Up @@ -895,6 +894,62 @@ public void When_no_exception_should_be_thrown_and_none_was_it_should_not_throw(
//-----------------------------------------------------------------------------------------------------------
foo.Invoking(f => f.Do()).Should().NotThrow();
}

private static void ThrowUntil(Stopwatch watch, int throwTime)
frederik-h marked this conversation as resolved.
Show resolved Hide resolved
{
if(watch.ElapsedMilliseconds <= throwTime)
throw new ArgumentException("An exception was forced");
}

[Fact]
public void When_no_exception_should_be_thrown_after_wait_time_but_it_was_it_should_throw()
{
//-----------------------------------------------------------------------------------------------------------
// Arrange
//-----------------------------------------------------------------------------------------------------------
var foo = A.Fake<IFoo>();
var watch = Stopwatch.StartNew();
var waitTime = 100;
var pollInterval = 10;

void ThrowLongerThanWaitTime() => ThrowUntil(watch, waitTime + waitTime / 2);

A.CallTo(() => foo.Do()).Invokes(ThrowLongerThanWaitTime);
frederik-h marked this conversation as resolved.
Show resolved Hide resolved
//-----------------------------------------------------------------------------------------------------------
// Act
//-----------------------------------------------------------------------------------------------------------
Action action = () => foo.Invoking(f => f.Do())
.Should().NotThrowAfter(waitTime, pollInterval, "we passed valid arguments");

//-----------------------------------------------------------------------------------------------------------
// Assert
//-----------------------------------------------------------------------------------------------------------
action
.Should().Throw<XunitException>()
.Where(e => e.Message.Contains(
frederik-h marked this conversation as resolved.
Show resolved Hide resolved
"Did not expect any exception after 100 milliseconds because we passed valid " +
"arguments, but found FakeItEasy.UserCallbackException")); }

[Fact]
public void When_no_exception_should_be_thrown_after_wait_time_and_none_was_it_should_not_throw()
{
//-----------------------------------------------------------------------------------------------------------
// Arrange
//-----------------------------------------------------------------------------------------------------------
var foo = A.Fake<IFoo>();
var watch = Stopwatch.StartNew();
var waitTime = 100;
var pollInterval = 10;

void ThrowShorterThanWaitTime() => ThrowUntil(watch, waitTime / 2);
frederik-h marked this conversation as resolved.
Show resolved Hide resolved

A.CallTo(() => foo.Do()).Invokes(ThrowShorterThanWaitTime);
//-----------------------------------------------------------------------------------------------------------
// Act / Assert
//-----------------------------------------------------------------------------------------------------------
foo.Invoking(f => f.Do()).Should().NotThrowAfter(waitTime, pollInterval);
}

}

#endregion
Expand Down