Skip to content

Commit

Permalink
Non-generic overload for WithInnerExceptionExactly (#1769)
Browse files Browse the repository at this point in the history
  • Loading branch information
karenfarnes committed Jan 13, 2022
1 parent f912dbe commit b02abf4
Show file tree
Hide file tree
Showing 10 changed files with 323 additions and 36 deletions.
44 changes: 44 additions & 0 deletions Src/FluentAssertions/ExceptionAssertionsExtensions.cs
Expand Up @@ -80,6 +80,28 @@ public static class ExceptionAssertionsExtensions
return (await task).WithInnerException<TInnerException>(because, becauseArgs);
}

/// <summary>
/// Asserts that the thrown exception contains an inner exception of type <param name="innerException" />.
/// </summary>
/// <typeparam name="TException">The expected type of the exception.</typeparam>
/// <param name="task">The <see cref="ExceptionAssertions{TException}"/> containing the thrown exception.</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 <paramref name="because" />.
/// </param>
public static async Task<ExceptionAssertions<Exception>> WithInnerException<TException>(
this Task<ExceptionAssertions<TException>> task,
Type innerException,
string because = "",
params object[] becauseArgs)
where TException : Exception
{
return (await task).WithInnerException(innerException, because, becauseArgs);
}

/// <summary>
/// Asserts that the thrown exception contains an inner exception of the exact type <typeparamref name="TInnerException" /> (and not a derived exception type).
/// </summary>
Expand All @@ -103,6 +125,28 @@ public static class ExceptionAssertionsExtensions
return (await task).WithInnerExceptionExactly<TInnerException>(because, becauseArgs);
}

/// <summary>
/// Asserts that the thrown exception contains an inner exception of the exact type <param name="innerException" /> (and not a derived exception type).
/// </summary>
/// <typeparam name="TException">The expected type of the exception.</typeparam>
/// <param name="task">The <see cref="ExceptionAssertions{TException}"/> containing the thrown exception.</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 <paramref name="because" />.
/// </param>
public static async Task<ExceptionAssertions<Exception>> WithInnerExceptionExactly<TException>(
this Task<ExceptionAssertions<TException>> task,
Type innerException,
string because = "",
params object[] becauseArgs)
where TException : Exception
{
return (await task).WithInnerExceptionExactly(innerException, because, becauseArgs);
}

/// <summary>
/// Asserts that the thrown exception has a parameter which name matches <paramref name="paramName" />.
/// </summary>
Expand Down
120 changes: 84 additions & 36 deletions Src/FluentAssertions/Specialized/ExceptionAssertions.cs
Expand Up @@ -3,9 +3,7 @@
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;

using FluentAssertions.Common;
using FluentAssertions.Equivalency;
using FluentAssertions.Equivalency.Steps;
using FluentAssertions.Execution;
using FluentAssertions.Formatting;
Expand Down Expand Up @@ -89,7 +87,8 @@ public ExceptionAssertions(IEnumerable<TException> exceptions)
.ForCondition(Subject.Any())
.FailWith("Expected exception with message {0}{reason}, but no exception was thrown.", expectedWildcardPattern);

OuterMessageAssertion.Execute(Subject.Select(exc => exc.Message).ToArray(), expectedWildcardPattern, because, becauseArgs);
OuterMessageAssertion.Execute(Subject.Select(exc => exc.Message).ToArray(), expectedWildcardPattern, because,
becauseArgs);

return this;
}
Expand All @@ -109,28 +108,24 @@ public ExceptionAssertions(IEnumerable<TException> exceptions)
params object[] becauseArgs)
where TInnerException : Exception
{
Execute.Assertion
.BecauseOf(because, becauseArgs)
.WithExpectation("Expected inner {0}{reason}, but ", typeof(TInnerException))
.ForCondition(Subject is not null)
.FailWith("no exception was thrown.")
.Then
.ForCondition(Subject.Any(e => e.InnerException is not null))
.FailWith("the thrown exception has no inner exception.")
.Then
.ClearExpectation();

TInnerException[] expectedInnerExceptions = Subject
.Select(e => e.InnerException)
.OfType<TInnerException>()
.ToArray();

Execute.Assertion
.ForCondition(expectedInnerExceptions.Any())
.BecauseOf(because, becauseArgs)
.FailWith("Expected inner {0}{reason}, but found {1}.", typeof(TInnerException), SingleSubject.InnerException);
var expectedInnerExceptions = AssertInnerExceptions(typeof(TInnerException), because, becauseArgs);
return new ExceptionAssertions<TInnerException>(expectedInnerExceptions.Cast<TInnerException>());
}

return new ExceptionAssertions<TInnerException>(expectedInnerExceptions);
/// <summary>
/// Asserts that the thrown exception contains an inner exception of type <param name="innerException" />.
/// </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 <paramref name="because" />.
/// </param>
public ExceptionAssertions<Exception> WithInnerException(Type innerException, string because = null,
params object[] becauseArgs)
{
return new ExceptionAssertions<Exception>(AssertInnerExceptions(innerException, because, becauseArgs));
}

/// <summary>
Expand All @@ -148,19 +143,24 @@ public ExceptionAssertions(IEnumerable<TException> exceptions)
params object[] becauseArgs)
where TInnerException : Exception
{
WithInnerException<TInnerException>(because, becauseArgs);

TInnerException[] expectedExceptions = Subject
.Select(e => e.InnerException)
.OfType<TInnerException>()
.Where(e => e?.GetType() == typeof(TInnerException)).ToArray();

Execute.Assertion
.ForCondition(expectedExceptions.Any())
.BecauseOf(because, becauseArgs)
.FailWith("Expected inner {0}{reason}, but found {1}.", typeof(TInnerException), SingleSubject.InnerException);
var exceptionExpression = AssertInnerExceptionExactly(typeof(TInnerException), because, becauseArgs);
return new ExceptionAssertions<TInnerException>(exceptionExpression.Cast<TInnerException>());
}

return new ExceptionAssertions<TInnerException>(expectedExceptions);
/// <summary>
/// Asserts that the thrown exception contains an inner exception of the exact type <param name="innerException" /> (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 <paramref name="because" />.
/// </param>
public ExceptionAssertions<Exception> WithInnerExceptionExactly(Type innerException, string because = null,
params object[] becauseArgs)
{
return new ExceptionAssertions<Exception>(AssertInnerExceptionExactly(innerException, because, becauseArgs));
}

/// <summary>
Expand Down Expand Up @@ -191,6 +191,54 @@ public ExceptionAssertions(IEnumerable<TException> exceptions)
return this;
}

private IEnumerable<Exception> AssertInnerExceptionExactly(Type innerException, string because = null,
params object[] becauseArgs)
{
Guard.ThrowIfArgumentIsNull(innerException, nameof(innerException));

AssertInnerExceptions(innerException, because, becauseArgs);

Exception[] expectedExceptions = Subject
.Select(e => e.InnerException)
.Where(e => e?.GetType() == innerException).ToArray();

Execute.Assertion
.ForCondition(expectedExceptions.Any())
.BecauseOf(because, becauseArgs)
.FailWith("Expected inner {0}{reason}, but found {1}.", innerException, SingleSubject.InnerException);

return expectedExceptions;
}

private IEnumerable<Exception> AssertInnerExceptions(Type innerException, string because = null,
params object[] becauseArgs)
{
Guard.ThrowIfArgumentIsNull(innerException, nameof(innerException));

Execute.Assertion
.BecauseOf(because, becauseArgs)
.WithExpectation("Expected inner {0}{reason}, but ", innerException)
.ForCondition(Subject is not null)
.FailWith("no exception was thrown.")
.Then
.ForCondition(Subject.Any(e => e.InnerException is not null))
.FailWith("the thrown exception has no inner exception.")
.Then
.ClearExpectation();

Exception[] expectedInnerExceptions = Subject
.Select(e => e.InnerException)
.Where(e => e != null && e.GetType().IsSameOrInherits(innerException))
.ToArray();

Execute.Assertion
.ForCondition(expectedInnerExceptions.Any())
.BecauseOf(because, becauseArgs)
.FailWith("Expected inner {0}{reason}, but found {1}.", innerException, SingleSubject.InnerException);

return expectedInnerExceptions;
}

private TException SingleSubject
{
get
Expand Down
Expand Up @@ -242,9 +242,13 @@ namespace FluentAssertions
{
public static System.Threading.Tasks.Task<FluentAssertions.Specialized.ExceptionAssertions<TException>> Where<TException>(this System.Threading.Tasks.Task<FluentAssertions.Specialized.ExceptionAssertions<TException>> task, System.Linq.Expressions.Expression<System.Func<TException, bool>> exceptionExpression, string because = "", params object[] becauseArgs)
where TException : System.Exception { }
public static System.Threading.Tasks.Task<FluentAssertions.Specialized.ExceptionAssertions<System.Exception>> WithInnerException<TException>(this System.Threading.Tasks.Task<FluentAssertions.Specialized.ExceptionAssertions<TException>> task, System.Type innerException, string because = "", params object[] becauseArgs)
where TException : System.Exception { }
public static System.Threading.Tasks.Task<FluentAssertions.Specialized.ExceptionAssertions<TInnerException>> WithInnerException<TException, TInnerException>(this System.Threading.Tasks.Task<FluentAssertions.Specialized.ExceptionAssertions<TException>> task, string because = "", params object[] becauseArgs)
where TException : System.Exception
where TInnerException : System.Exception { }
public static System.Threading.Tasks.Task<FluentAssertions.Specialized.ExceptionAssertions<System.Exception>> WithInnerExceptionExactly<TException>(this System.Threading.Tasks.Task<FluentAssertions.Specialized.ExceptionAssertions<TException>> task, System.Type innerException, string because = "", params object[] becauseArgs)
where TException : System.Exception { }
public static System.Threading.Tasks.Task<FluentAssertions.Specialized.ExceptionAssertions<TInnerException>> WithInnerExceptionExactly<TException, TInnerException>(this System.Threading.Tasks.Task<FluentAssertions.Specialized.ExceptionAssertions<TException>> task, string because = "", params object[] becauseArgs)
where TException : System.Exception
where TInnerException : System.Exception { }
Expand Down Expand Up @@ -2209,8 +2213,10 @@ namespace FluentAssertions.Specialized
protected override string Identifier { get; }
public TException Which { get; }
public FluentAssertions.Specialized.ExceptionAssertions<TException> Where(System.Linq.Expressions.Expression<System.Func<TException, bool>> exceptionExpression, string because = "", params object[] becauseArgs) { }
public FluentAssertions.Specialized.ExceptionAssertions<System.Exception> WithInnerException(System.Type innerException, string because = null, params object[] becauseArgs) { }
public virtual FluentAssertions.Specialized.ExceptionAssertions<TInnerException> WithInnerException<TInnerException>(string because = null, params object[] becauseArgs)
where TInnerException : System.Exception { }
public FluentAssertions.Specialized.ExceptionAssertions<System.Exception> WithInnerExceptionExactly(System.Type innerException, string because = null, params object[] becauseArgs) { }
public virtual FluentAssertions.Specialized.ExceptionAssertions<TInnerException> WithInnerExceptionExactly<TInnerException>(string because = null, params object[] becauseArgs)
where TInnerException : System.Exception { }
public virtual FluentAssertions.Specialized.ExceptionAssertions<TException> WithMessage(string expectedWildcardPattern, string because = "", params object[] becauseArgs) { }
Expand Down
Expand Up @@ -242,9 +242,13 @@ namespace FluentAssertions
{
public static System.Threading.Tasks.Task<FluentAssertions.Specialized.ExceptionAssertions<TException>> Where<TException>(this System.Threading.Tasks.Task<FluentAssertions.Specialized.ExceptionAssertions<TException>> task, System.Linq.Expressions.Expression<System.Func<TException, bool>> exceptionExpression, string because = "", params object[] becauseArgs)
where TException : System.Exception { }
public static System.Threading.Tasks.Task<FluentAssertions.Specialized.ExceptionAssertions<System.Exception>> WithInnerException<TException>(this System.Threading.Tasks.Task<FluentAssertions.Specialized.ExceptionAssertions<TException>> task, System.Type innerException, string because = "", params object[] becauseArgs)
where TException : System.Exception { }
public static System.Threading.Tasks.Task<FluentAssertions.Specialized.ExceptionAssertions<TInnerException>> WithInnerException<TException, TInnerException>(this System.Threading.Tasks.Task<FluentAssertions.Specialized.ExceptionAssertions<TException>> task, string because = "", params object[] becauseArgs)
where TException : System.Exception
where TInnerException : System.Exception { }
public static System.Threading.Tasks.Task<FluentAssertions.Specialized.ExceptionAssertions<System.Exception>> WithInnerExceptionExactly<TException>(this System.Threading.Tasks.Task<FluentAssertions.Specialized.ExceptionAssertions<TException>> task, System.Type innerException, string because = "", params object[] becauseArgs)
where TException : System.Exception { }
public static System.Threading.Tasks.Task<FluentAssertions.Specialized.ExceptionAssertions<TInnerException>> WithInnerExceptionExactly<TException, TInnerException>(this System.Threading.Tasks.Task<FluentAssertions.Specialized.ExceptionAssertions<TException>> task, string because = "", params object[] becauseArgs)
where TException : System.Exception
where TInnerException : System.Exception { }
Expand Down Expand Up @@ -2209,8 +2213,10 @@ namespace FluentAssertions.Specialized
protected override string Identifier { get; }
public TException Which { get; }
public FluentAssertions.Specialized.ExceptionAssertions<TException> Where(System.Linq.Expressions.Expression<System.Func<TException, bool>> exceptionExpression, string because = "", params object[] becauseArgs) { }
public FluentAssertions.Specialized.ExceptionAssertions<System.Exception> WithInnerException(System.Type innerException, string because = null, params object[] becauseArgs) { }
public virtual FluentAssertions.Specialized.ExceptionAssertions<TInnerException> WithInnerException<TInnerException>(string because = null, params object[] becauseArgs)
where TInnerException : System.Exception { }
public FluentAssertions.Specialized.ExceptionAssertions<System.Exception> WithInnerExceptionExactly(System.Type innerException, string because = null, params object[] becauseArgs) { }
public virtual FluentAssertions.Specialized.ExceptionAssertions<TInnerException> WithInnerExceptionExactly<TInnerException>(string because = null, params object[] becauseArgs)
where TInnerException : System.Exception { }
public virtual FluentAssertions.Specialized.ExceptionAssertions<TException> WithMessage(string expectedWildcardPattern, string because = "", params object[] becauseArgs) { }
Expand Down

0 comments on commit b02abf4

Please sign in to comment.