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

Should().Throw and Should().NotThrow throw for Func<T> #951

Merged
merged 14 commits into from Nov 8, 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
12 changes: 11 additions & 1 deletion Src/FluentAssertions/AssertionExtensions.cs
Expand Up @@ -634,14 +634,24 @@ public static ActionAssertions Should(this Action action)

/// <summary>
/// Returns a <see cref="AsyncFunctionAssertions"/> object that can be used to assert the
/// current <see cref="System.Func{T}"/> .
/// current <see cref="System.Func{Task}"/> .
/// </summary>
[Pure]
public static AsyncFunctionAssertions Should(this Func<Task> action)
{
return new AsyncFunctionAssertions(action, extractor);
}

/// <summary>
/// Returns a <see cref="FunctionAssertions{T}"/> object that can be used to assert the
/// current <see cref="System.Func{T}"/> .
/// </summary>
[Pure]
public static FunctionAssertions<T> Should<T>(this Func<T> func)
{
return new FunctionAssertions<T>(func, extractor);
}

#if NET45 || NET47 || NETCOREAPP2_0

/// <summary>
Expand Down
175 changes: 175 additions & 0 deletions Src/FluentAssertions/Specialized/FunctionAssertions.cs
@@ -0,0 +1,175 @@
using System;
using System.Diagnostics;
using System.Linq;

using FluentAssertions.Execution;
using FluentAssertions.Primitives;

namespace FluentAssertions.Specialized
{
/// <summary>
/// Contains a number of methods to assert that a synchronous function yields the expected result.
/// </summary>
[DebuggerNonUserCode]
public class FunctionAssertions<T> : ReferenceTypeAssertions<Func<T>, FunctionAssertions<T>>
{
private readonly IExtractExceptions extractor;

public FunctionAssertions(Func<T> subject, IExtractExceptions extractor)
{
this.extractor = extractor;
Subject = subject;
}

protected override string Identifier => "function";

/// <summary>
/// Asserts that the current <see cref="Func{T}"/> 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 ExceptionAssertions<TException> Throw<TException>(string because = "", params object[] becauseArgs)
where TException : Exception
{
Exception exception = InvokeSubjectWithInterception();
return Throw<TException>(exception, because, becauseArgs);
}

/// <summary>
/// Asserts that the current <see cref="Func{T}"/> throws an exception of the exact type <typeparamref name="TException"/> (and not a derived exception type).
/// </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 ExceptionAssertions<TException> ThrowExactly<TException>(string because = "", params object[] becauseArgs)
where TException : Exception
{
var exceptionAssertions = Throw<TException>(because, becauseArgs);
exceptionAssertions.Which.GetType().Should().Be<TException>(because, becauseArgs);
return exceptionAssertions;
}

/// <summary>
/// Asserts that the current <see cref="Func{T}"/> 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 AndWhichConstraint<FunctionAssertions<T>, T> NotThrow(string because = "", params object[] becauseArgs)
{
try
{
T result = Subject();
return new AndWhichConstraint<FunctionAssertions<T>, T>(this, result);
}
catch (Exception exception)
{
NotThrow(exception, because, becauseArgs);
return null;

}
}

/// <summary>
/// Asserts that the current <see cref="Func{T}"/> 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 void NotThrow<TException>(string because = "", params object[] becauseArgs)
where TException : Exception
{
try
{
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());
}
}

private static Exception GetFirstNonAggregateException(Exception exception)
{
Exception nonAggregateException = exception;
while (nonAggregateException is AggregateException)
{
nonAggregateException = nonAggregateException.InnerException;
krajek marked this conversation as resolved.
Show resolved Hide resolved
}

return nonAggregateException;
}

private ExceptionAssertions<TException> Throw<TException>(Exception exception, string because, object[] becauseArgs)
where TException : Exception
{
Execute.Assertion
krajek marked this conversation as resolved.
Show resolved Hide resolved
.ForCondition(exception != null)
.BecauseOf(because, becauseArgs)
.FailWith("Expected {0}{reason}, but no exception was thrown.", typeof(TException));

var exceptions = extractor.OfType<TException>(exception);

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
{
Subject();
return null;
}
catch (Exception exception)
{
return exception;
}
}
}
}