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 ThrowAsync, ThrowExactlyAsync and NotThrowAsync #931

Merged
merged 4 commits into from Oct 1, 2018
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
26 changes: 26 additions & 0 deletions Src/FluentAssertions/AssertionExtensions.Actions.cs
Expand Up @@ -64,6 +64,32 @@ public static partial class AssertionExtensions
return exceptionAssertions;
}

/// <summary>
/// Asserts that the <paramref name="asyncActionAssertions"/> subject throws the exact exception (and not a derived exception type).
/// </summary>
/// <param name="asyncActionAssertions">A reference to the method or property.</param>
/// <typeparam name="TException">
/// The type of the exception it should throw.
/// </typeparam>
/// <param name="because">
/// A formatted phrase explaining why the assertion should be satisfied. If the phrase does not
/// start with the word <i>because</i>, it is prepended to the message.
/// </param>
/// <param name="becauseArgs">
/// Zero or more values to use for filling in any <see cref="string.Format(string,object[])"/> compatible placeholders.
/// </param>
/// <returns>
/// Returns an object that allows asserting additional members of the thrown exception.
/// </returns>
public static async Task<ExceptionAssertions<TException>> ThrowExactlyAsync<TException>(this AsyncFunctionAssertions asyncActionAssertions, string because = "",
params object[] becauseArgs)
where TException : Exception
{
var exceptionAssertions = await asyncActionAssertions.ThrowAsync<TException>(because, becauseArgs);
exceptionAssertions.Which.GetType().Should().Be<TException>(because, becauseArgs);
return exceptionAssertions;
}

private class AggregateExceptionExtractor : IExtractExceptions
{
public IEnumerable<T> OfType<T>(Exception actualException)
Expand Down
171 changes: 132 additions & 39 deletions Src/FluentAssertions/Specialized/AsyncFunctionAssertions.cs
Expand Up @@ -40,19 +40,24 @@ public ExceptionAssertions<TException> Throw<TException>(string because = "", pa
where TException : Exception
{
Exception exception = InvokeSubjectWithInterception();
var exceptions = extractor.OfType<TException>(exception);

Execute.Assertion
.ForCondition(exception != null)
.BecauseOf(because, becauseArgs)
.FailWith("Expected {0}{reason}, but no exception was thrown.", typeof(TException));

Execute.Assertion
.ForCondition(exceptions.Any())
.BecauseOf(because, becauseArgs)
.FailWith("Expected {0}{reason}, but found {1}.", typeof(TException), exception);
return Throw<TException>(exception, because, becauseArgs);
}

return new ExceptionAssertions<TException>(exceptions);
/// <summary>
/// Asserts that the current <see cref="Func{Task}"/> throws an exception of type <typeparamref name="TException"/>.
/// </summary>
/// <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>
public async Task<ExceptionAssertions<TException>> ThrowAsync<TException>(string because = "", params object[] becauseArgs)
where TException : Exception
{
Exception exception = await InvokeSubjectWithInterceptionAsync();
return Throw<TException>(exception, because, becauseArgs);
}

/// <summary>
Expand All @@ -73,12 +78,29 @@ public void NotThrow(string because = "", params object[] becauseArgs)
}
catch (Exception exception)
{
Exception nonAggregateException = GetFirstNonAggregateException(exception);
NotThrow(exception, because, becauseArgs);
}
}

Execute.Assertion
.BecauseOf(because, becauseArgs)
.FailWith("Did not expect any exception{reason}, but found a {0} with message {1}.",
nonAggregateException.GetType(), nonAggregateException.ToString());
/// <summary>
/// Asserts that the current <see cref="Func{Task}"/> does not throw any exception.
/// </summary>
/// <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>
public async Task NotThrowAsync(string because = "", params object[] becauseArgs)
{
try
{
await Task.Run(Subject);
}
catch (Exception exception)
{
NotThrow(exception, because, becauseArgs);
}
}

Expand All @@ -101,16 +123,54 @@ public void NotThrow<TException>(string because = "", params object[] becauseArg
}
catch (Exception exception)
{
Exception nonAggregateException = GetFirstNonAggregateException(exception);

if (nonAggregateException != null)
{
Execute.Assertion
.ForCondition(!(nonAggregateException is TException))
.BecauseOf(because, becauseArgs)
.FailWith("Did not expect {0}{reason}, but found one with message {1}.",
typeof(TException), nonAggregateException.ToString());
}
NotThrow<TException>(exception, because, becauseArgs);
}
}

/// <summary>
/// Asserts that the current <see cref="Func{Task}"/> does not throw an exception of type <typeparamref name="TException"/>.
/// </summary>
/// <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>
public async Task NotThrowAsync<TException>(string because = "", params object[] becauseArgs)
where TException : Exception
{
try
{
await Task.Run(Subject);
}
catch (Exception exception)
{
NotThrow<TException>(exception, because, becauseArgs);
}
}

private static void NotThrow(Exception exception, string because, object[] becauseArgs)
{
Exception nonAggregateException = GetFirstNonAggregateException(exception);

Execute.Assertion
.BecauseOf(because, becauseArgs)
.FailWith("Did not expect any exception{reason}, but found a {0} with message {1}.",
nonAggregateException.GetType(), nonAggregateException.ToString());
}

private static void NotThrow<TException>(Exception exception, string because, object[] becauseArgs) where TException : Exception
{
Exception nonAggregateException = GetFirstNonAggregateException(exception);

if (nonAggregateException != null)
{
Execute.Assertion
.ForCondition(!(nonAggregateException is TException))
.BecauseOf(because, becauseArgs)
.FailWith("Did not expect {0}{reason}, but found one with message {1}.",
typeof(TException), nonAggregateException.ToString());
}
}

Expand All @@ -125,28 +185,61 @@ private static Exception GetFirstNonAggregateException(Exception exception)
return nonAggregateException;
}

private Exception InvokeSubjectWithInterception()
private ExceptionAssertions<TException> Throw<TException>(Exception exception, string because, object[] becauseArgs)
where TException : Exception
{
Exception actualException = null;
var exceptions = extractor.OfType<TException>(exception);

Execute.Assertion
.ForCondition(exception != null)
.BecauseOf(because, becauseArgs)
.FailWith("Expected {0}{reason}, but no exception was thrown.", typeof(TException));

Execute.Assertion
.ForCondition(exceptions.Any())
.BecauseOf(because, becauseArgs)
.FailWith("Expected {0}{reason}, but found {1}.", typeof(TException), exception);

return new ExceptionAssertions<TException>(exceptions);
}

private Exception InvokeSubjectWithInterception()
{
try
{
Task.Run(Subject).Wait();
}
catch (Exception exception)
{
var ar = exception as AggregateException;
if (ar?.InnerException is AggregateException)
{
actualException = ar.InnerException;
}
else
{
actualException = exception;
}
return InterceptException(exception);
}

return null;
}

private async Task<Exception> InvokeSubjectWithInterceptionAsync()
{
try
{
await Task.Run(Subject);
}
catch (Exception exception)
{
return InterceptException(exception);
}

return null;
}

private Exception InterceptException(Exception exception)
{
var ar = exception as AggregateException;
krajek marked this conversation as resolved.
Show resolved Hide resolved
if (ar?.InnerException is AggregateException)
{
return ar.InnerException;
}

return actualException;
return exception;
}
}
}
117 changes: 117 additions & 0 deletions Tests/Shared.Specs/AsyncFunctionExceptionAssertionSpecs.cs
Expand Up @@ -67,6 +67,26 @@ public void When_async_method_throws_expected_exception_it_should_succeed()
action.Should().NotThrow();
}

[Fact]
public async void When_async_method_throws_async_expected_exception_it_should_succeed()
{
//-----------------------------------------------------------------------------------------------------------
// Arrange
//-----------------------------------------------------------------------------------------------------------
var asyncObject = new AsyncClass();

//-----------------------------------------------------------------------------------------------------------
// Act
//-----------------------------------------------------------------------------------------------------------
Func<Task> action = async () => await asyncObject.ThrowAsync<ArgumentException>();

//-----------------------------------------------------------------------------------------------------------
// Assert
//-----------------------------------------------------------------------------------------------------------
await action.Should().ThrowAsync<ArgumentException>();

}

[Fact]
public void When_async_method_does_not_throw_expected_exception_it_should_fail()
{
Expand Down Expand Up @@ -132,6 +152,83 @@ public void When_async_method_does_not_throw_exception_and_that_was_expected_it_
action.Should().NotThrow();
}

[Fact]
public async Task When_async_method_does_not_throw_async_exception_and_that_was_expected_it_should_succeed()
{
//-----------------------------------------------------------------------------------------------------------
// Arrange
//-----------------------------------------------------------------------------------------------------------
var asyncObject = new AsyncClass();

//-----------------------------------------------------------------------------------------------------------
// Act
//-----------------------------------------------------------------------------------------------------------
Func<Task> action = async () => await asyncObject.SucceedAsync();

//-----------------------------------------------------------------------------------------------------------
// Assert
//-----------------------------------------------------------------------------------------------------------
await action.Should().NotThrowAsync();
}

[Fact]
public async Task When_subject_throws_subclass_of_expected_async_exception_it_should_succeed()
{
//-----------------------------------------------------------------------------------------------------------
// Arrange
//-----------------------------------------------------------------------------------------------------------
var asyncObject = new AsyncClass();

//-----------------------------------------------------------------------------------------------------------
// Act
//-----------------------------------------------------------------------------------------------------------
Func<Task> action = async () => await asyncObject.ThrowAsync<ArgumentNullException>();

//-----------------------------------------------------------------------------------------------------------
// Assert
//-----------------------------------------------------------------------------------------------------------
await action.Should().ThrowAsync<ArgumentException>("because {0} should do that", "IFoo.Do");
}

[Fact]
public async Task When_subject_throws_subclass_of_expected_async_exact_exception_it_should_throw()
{
//-----------------------------------------------------------------------------------------------------------
// Arrange
//-----------------------------------------------------------------------------------------------------------
var asyncObject = new AsyncClass();

//-----------------------------------------------------------------------------------------------------------
// Act
//-----------------------------------------------------------------------------------------------------------
Func<Task> action = async () => await asyncObject.ThrowAsync<ArgumentNullException>();
Func<Task> testAction = async () => await action.Should().ThrowExactlyAsync<ArgumentException>("ABCDE");

//-----------------------------------------------------------------------------------------------------------
// Assert
//-----------------------------------------------------------------------------------------------------------
(await testAction.Should().ThrowAsync<XunitException>()).WithMessage("*ArgumentException*ABCDE*ArgumentNullException*");
}

[Fact]
public async Task When_subject_throws_expected_async_exact_exception_it_should_succeed()
{
//-----------------------------------------------------------------------------------------------------------
// Arrange
//-----------------------------------------------------------------------------------------------------------
var asyncObject = new AsyncClass();

//-----------------------------------------------------------------------------------------------------------
// Act
//-----------------------------------------------------------------------------------------------------------
Func<Task> action = async () => await asyncObject.ThrowAsync<ArgumentException>();

//-----------------------------------------------------------------------------------------------------------
// Assert
//-----------------------------------------------------------------------------------------------------------
await action.Should().ThrowExactlyAsync<ArgumentException>("because {0} should do that", "IFoo.Do");
}

[Fact]
public void When_async_method_throws_exception_and_no_exception_was_expected_it_should_fail()
{
Expand Down Expand Up @@ -175,6 +272,26 @@ public void When_async_method_throws_exception_and_expected_not_to_throw_another
action.Should().NotThrow();
}

[Fact]
public async Task When_async_method_throws_exception_and_expected_not_to_throw_async_another_one_it_should_succeed()
{
//-----------------------------------------------------------------------------------------------------------
// Arrange
//-----------------------------------------------------------------------------------------------------------
var asyncObject = new AsyncClass();

//-----------------------------------------------------------------------------------------------------------
// Act
//-----------------------------------------------------------------------------------------------------------
Func<Task> action = async () => await asyncObject.ThrowAsync<ArgumentException>();


//-----------------------------------------------------------------------------------------------------------
// Assert
//-----------------------------------------------------------------------------------------------------------
await action.Should().NotThrowAsync<InvalidOperationException>();
}

[Fact]
public void When_async_method_succeeds_and_expected_not_to_throw_particular_exception_it_should_succeed()
{
Expand Down
2 changes: 2 additions & 0 deletions docs/_pages/documentation.md
Expand Up @@ -714,6 +714,8 @@ Talking about the `async` keyword, you can also verify that an asynchronously ex

```csharp
Func<Task> act = async () => { await asyncObject.ThrowAsync<ArgumentException>(); };
await act.Should().ThrowAsync<InvalidOperationException>();
await act.Should().NotThrowAsync();
act.Should().Throw<InvalidOperationException>();
act.Should().NotThrow();
```
Expand Down