Skip to content

Commit

Permalink
Should().Throw and Should().NotThrow throw for Func<T> (#951)
Browse files Browse the repository at this point in the history
  • Loading branch information
krajek authored and dennisdoomen committed Nov 8, 2018
1 parent a9abfa8 commit a1673b0
Show file tree
Hide file tree
Showing 4 changed files with 575 additions and 1 deletion.
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;
}

return nonAggregateException;
}

private ExceptionAssertions<TException> Throw<TException>(Exception exception, string because, object[] becauseArgs)
where TException : Exception
{
Execute.Assertion
.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;
}
}
}
}

0 comments on commit a1673b0

Please sign in to comment.