Skip to content

Commit

Permalink
Null guards on funcs and expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
jnyrup committed Aug 3, 2019
1 parent 9a7b599 commit 9197d21
Show file tree
Hide file tree
Showing 20 changed files with 546 additions and 0 deletions.
10 changes: 10 additions & 0 deletions Src/FluentAssertions/Collections/CollectionAssertions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,8 @@ public AndConstraint<TAssertions> Equal(IEnumerable expected, string because = "
protected void AssertSubjectEquality<TActual, TExpected>(IEnumerable expectation, Func<TActual, TExpected, bool> equalityComparison,
string because = "", params object[] becauseArgs)
{
Guard.ThrowIfArgumentIsNull(equalityComparison, nameof(equalityComparison));

bool subjectIsNull = ReferenceEquals(Subject, null);
bool expectationIsNull = expectation is null;
if (subjectIsNull && expectationIsNull)
Expand Down Expand Up @@ -1416,6 +1418,8 @@ public AndConstraint<TAssertions> StartWith(object element, string because = "",

protected void AssertCollectionStartsWith<TActual, TExpected>(IEnumerable<TActual> actualItems, TExpected[] expected, Func<TActual, TExpected, bool> equalityComparison, string because = "", params object[] becauseArgs)
{
Guard.ThrowIfArgumentIsNull(equalityComparison, nameof(equalityComparison));

Execute.Assertion
.BecauseOf(because, becauseArgs)
.WithExpectation("Expected {context:collection} to start with {0}{reason}, ", expected)
Expand All @@ -1431,6 +1435,8 @@ public AndConstraint<TAssertions> StartWith(object element, string because = "",

protected void AssertCollectionStartsWith<TActual, TExpected>(IEnumerable<TActual> actualItems, ICollection<TExpected> expected, Func<TActual, TExpected, bool> equalityComparison, string because = "", params object[] becauseArgs)
{
Guard.ThrowIfArgumentIsNull(equalityComparison, nameof(equalityComparison));

Execute.Assertion
.BecauseOf(because, becauseArgs)
.WithExpectation("Expected {context:collection} to start with {0}{reason}, ", expected)
Expand Down Expand Up @@ -1466,6 +1472,8 @@ public AndConstraint<TAssertions> EndWith(object element, string because = "", p

protected void AssertCollectionEndsWith<TActual, TExpected>(IEnumerable<TActual> actual, TExpected[] expected, Func<TActual, TExpected, bool> equalityComparison, string because = "", params object[] becauseArgs)
{
Guard.ThrowIfArgumentIsNull(equalityComparison, nameof(equalityComparison));

Execute.Assertion
.BecauseOf(because, becauseArgs)
.WithExpectation("Expected {context:collection} to end with {0}{reason}, ", expected)
Expand All @@ -1486,6 +1494,8 @@ public AndConstraint<TAssertions> EndWith(object element, string because = "", p

protected void AssertCollectionEndsWith<TActual, TExpected>(IEnumerable<TActual> actual, ICollection<TExpected> expected, Func<TActual, TExpected, bool> equalityComparison, string because = "", params object[] becauseArgs)
{
Guard.ThrowIfArgumentIsNull(equalityComparison, nameof(equalityComparison));

Execute.Assertion
.BecauseOf(because, becauseArgs)
.WithExpectation("Expected {context:collection} to end with {0}{reason}, ", expected)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ public GenericCollectionAssertions(IEnumerable<T> actualValue)
public AndConstraint<GenericCollectionAssertions<T>> NotContainNulls<TKey>(Expression<Func<T, TKey>> predicate, string because = "", params object[] becauseArgs)
where TKey : class
{
Guard.ThrowIfArgumentIsNull(predicate, nameof(predicate));

if (Subject is null)
{
Execute.Assertion
Expand Down Expand Up @@ -72,6 +74,8 @@ public AndConstraint<GenericCollectionAssertions<T>> NotContainNulls<TKey>(Expre
/// </param>
public AndConstraint<GenericCollectionAssertions<T>> OnlyHaveUniqueItems<TKey>(Expression<Func<T, TKey>> predicate, string because = "", params object[] becauseArgs)
{
Guard.ThrowIfArgumentIsNull(predicate, nameof(predicate));

if (Subject is null)
{
Execute.Assertion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,8 @@ public AndConstraint<TAssertions> EndWith(IEnumerable<T> expectation, string bec
/// </param>
public AndWhichConstraint<TAssertions, T> Contain(Expression<Func<T, bool>> predicate, string because = "", params object[] becauseArgs)
{
Guard.ThrowIfArgumentIsNull(predicate, nameof(predicate));

if (Subject is null)
{
Execute.Assertion
Expand Down Expand Up @@ -478,6 +480,8 @@ public AndConstraint<TAssertions> EndWith(IEnumerable<T> expectation, string bec
public AndConstraint<TAssertions> OnlyContain(
Expression<Func<T, bool>> predicate, string because = "", params object[] becauseArgs)
{
Guard.ThrowIfArgumentIsNull(predicate, nameof(predicate));

Func<T, bool> compiledPredicate = predicate.Compile();

Execute.Assertion
Expand Down Expand Up @@ -555,6 +559,8 @@ public AndConstraint<TAssertions> NotContain(IEnumerable<T> unexpectedItemsList,
/// </param>
public AndConstraint<TAssertions> NotContain(Expression<Func<T, bool>> predicate, string because = "", params object[] becauseArgs)
{
Guard.ThrowIfArgumentIsNull(predicate, nameof(predicate));

if (Subject is null)
{
Execute.Assertion
Expand Down Expand Up @@ -624,6 +630,8 @@ public AndConstraint<TAssertions> NotContain(Expression<Func<T, bool>> predicate
public AndWhichConstraint<TAssertions, T> ContainSingle(Expression<Func<T, bool>> predicate,
string because = "", params object[] becauseArgs)
{
Guard.ThrowIfArgumentIsNull(predicate, nameof(predicate));

string expectationPrefix =
string.Format("Expected {{context:collection}} to contain a single item matching {0}{{reason}}, ", predicate.Body);

Expand Down
5 changes: 5 additions & 0 deletions Src/FluentAssertions/Equivalency/ConversionSelector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using FluentAssertions.Common;

namespace FluentAssertions.Equivalency
{
Expand Down Expand Up @@ -33,13 +34,17 @@ public void IncludeAll()

public void Include(Expression<Func<IMemberInfo, bool>> predicate)
{
Guard.ThrowIfArgumentIsNull(predicate, nameof(predicate));

inclusions.Add(new ConversionSelectorRule(
predicate.Compile(),
$"Try conversion of member {predicate.Body}. "));
}

public void Exclude(Expression<Func<IMemberInfo, bool>> predicate)
{
Guard.ThrowIfArgumentIsNull(predicate, nameof(predicate));

exclusions.Add(new ConversionSelectorRule(
predicate.Compile(),
$"Do not convert member {predicate.Body}."));
Expand Down
3 changes: 3 additions & 0 deletions Src/FluentAssertions/EventRaisingExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Linq.Expressions;
using FluentAssertions.Common;
using FluentAssertions.Events;
using FluentAssertions.Execution;

Expand Down Expand Up @@ -39,6 +40,8 @@ public static IEventRecorder WithSender(this IEventRecorder eventRecorder, objec
/// </summary>
public static IEventRecorder WithArgs<T>(this IEventRecorder eventRecorder, Expression<Func<T, bool>> predicate)
{
Guard.ThrowIfArgumentIsNull(predicate, nameof(predicate));

Func<T, bool> compiledPredicate = predicate.Compile();

if (!eventRecorder.First().Parameters.OfType<T>().Any())
Expand Down
5 changes: 5 additions & 0 deletions Src/FluentAssertions/Execution/GivenSelector.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Linq;
using FluentAssertions.Common;

namespace FluentAssertions.Execution
{
Expand Down Expand Up @@ -40,6 +41,8 @@ public GivenSelector(Func<T> selector, bool predecessorSucceeded, AssertionScope
/// </remarks>
public GivenSelector<T> ForCondition(Func<T, bool> predicate)
{
Guard.ThrowIfArgumentIsNull(predicate, nameof(predicate));

predecessor.ForCondition(predicate(subject));

return this;
Expand All @@ -57,6 +60,8 @@ public GivenSelector<T> ForCondition(Func<T, bool> predicate)
/// </remarks>
public GivenSelector<TOut> Given<TOut>(Func<T, TOut> selector)
{
Guard.ThrowIfArgumentIsNull(selector, nameof(selector));

return new GivenSelector<TOut>(() => selector(subject), predecessorSucceeded, predecessor);
}

Expand Down
2 changes: 2 additions & 0 deletions Src/FluentAssertions/Specialized/ExceptionAssertions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ public ExceptionAssertions(IEnumerable<TException> exceptions) : base(exceptions
public ExceptionAssertions<TException> Where(Expression<Func<TException, bool>> exceptionExpression,
string because = "", params object[] becauseArgs)
{
Guard.ThrowIfArgumentIsNull(exceptionExpression, nameof(exceptionExpression));

Func<TException, bool> condition = exceptionExpression.Compile();
Execute.Assertion
.ForCondition(condition(SingleSubject))
Expand Down
4 changes: 4 additions & 0 deletions Src/FluentAssertions/Types/MemberInfoAssertions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ protected MemberInfoAssertions(TSubject subject) : base(subject)
string because = "", params object[] becauseArgs)
where TAttribute : Attribute
{
Guard.ThrowIfArgumentIsNull(isMatchingAttributePredicate, nameof(isMatchingAttributePredicate));

string failureMessage = string.Format("Expected {0} {1}" +
" to be decorated with {2}{{reason}}, but that attribute was not found.",
Identifier, SubjectDescription, typeof(TAttribute));
Expand Down Expand Up @@ -112,6 +114,8 @@ protected MemberInfoAssertions(TSubject subject) : base(subject)
string because = "", params object[] becauseArgs)
where TAttribute : Attribute
{
Guard.ThrowIfArgumentIsNull(isMatchingAttributePredicate, nameof(isMatchingAttributePredicate));

string failureMessage = string.Format("Expected {0} {1}" +
" to not be decorated with {2}{{reason}}, but that attribute was found.",
Identifier, SubjectDescription, typeof(TAttribute));
Expand Down
4 changes: 4 additions & 0 deletions Src/FluentAssertions/Types/MethodInfoSelectorAssertions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ public AndConstraint<MethodInfoSelectorAssertions> BeDecoratedWith<TAttribute>(s
Expression<Func<TAttribute, bool>> isMatchingAttributePredicate, string because = "", params object[] becauseArgs)
where TAttribute : Attribute
{
Guard.ThrowIfArgumentIsNull(isMatchingAttributePredicate, nameof(isMatchingAttributePredicate));

IEnumerable<MethodInfo> methodsWithoutAttribute = GetMethodsWithout<TAttribute>(isMatchingAttributePredicate);

string failureMessage =
Expand Down Expand Up @@ -186,6 +188,8 @@ public AndConstraint<MethodInfoSelectorAssertions> NotBeDecoratedWith<TAttribute
Expression<Func<TAttribute, bool>> isMatchingAttributePredicate, string because = "", params object[] becauseArgs)
where TAttribute : Attribute
{
Guard.ThrowIfArgumentIsNull(isMatchingAttributePredicate, nameof(isMatchingAttributePredicate));

IEnumerable<MethodInfo> methodsWithAttribute = GetMethodsWith<TAttribute>(isMatchingAttributePredicate);

string failureMessage =
Expand Down
8 changes: 8 additions & 0 deletions Src/FluentAssertions/Types/TypeAssertions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,8 @@ public AndConstraint<TypeAssertions> BeDecoratedWith<TAttribute>(string because
Expression<Func<TAttribute, bool>> isMatchingAttributePredicate, string because = "", params object[] becauseArgs)
where TAttribute : Attribute
{
Guard.ThrowIfArgumentIsNull(isMatchingAttributePredicate, nameof(isMatchingAttributePredicate));

BeDecoratedWith<TAttribute>(because, becauseArgs);

Execute.Assertion
Expand Down Expand Up @@ -300,6 +302,8 @@ public AndConstraint<TypeAssertions> BeDecoratedWithOrInherit<TAttribute>(string
Expression<Func<TAttribute, bool>> isMatchingAttributePredicate, string because = "", params object[] becauseArgs)
where TAttribute : Attribute
{
Guard.ThrowIfArgumentIsNull(isMatchingAttributePredicate, nameof(isMatchingAttributePredicate));

BeDecoratedWithOrInherit<TAttribute>(because, becauseArgs);

Execute.Assertion
Expand Down Expand Up @@ -351,6 +355,8 @@ public AndConstraint<TypeAssertions> NotBeDecoratedWith<TAttribute>(string becau
Expression<Func<TAttribute, bool>> isMatchingAttributePredicate, string because = "", params object[] becauseArgs)
where TAttribute : Attribute
{
Guard.ThrowIfArgumentIsNull(isMatchingAttributePredicate, nameof(isMatchingAttributePredicate));

Execute.Assertion
.ForCondition(!Subject.HasMatchingAttribute(isMatchingAttributePredicate))
.BecauseOf(because, becauseArgs)
Expand Down Expand Up @@ -400,6 +406,8 @@ public AndConstraint<TypeAssertions> NotBeDecoratedWithOrInherit<TAttribute>(str
Expression<Func<TAttribute, bool>> isMatchingAttributePredicate, string because = "", params object[] becauseArgs)
where TAttribute : Attribute
{
Guard.ThrowIfArgumentIsNull(isMatchingAttributePredicate, nameof(isMatchingAttributePredicate));

Execute.Assertion
.ForCondition(!Subject.HasMatchingAttribute(isMatchingAttributePredicate, true))
.BecauseOf(because, becauseArgs)
Expand Down
8 changes: 8 additions & 0 deletions Src/FluentAssertions/Types/TypeSelectorAssertions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ public AndConstraint<TypeSelectorAssertions> BeDecoratedWith<TAttribute>(string
Expression<Func<TAttribute, bool>> isMatchingAttributePredicate, string because = "", params object[] becauseArgs)
where TAttribute : Attribute
{
Guard.ThrowIfArgumentIsNull(isMatchingAttributePredicate, nameof(isMatchingAttributePredicate));

Type[] typesWithoutMatchingAttribute = Subject
.Where(type => !type.HasMatchingAttribute(isMatchingAttributePredicate))
.ToArray();
Expand Down Expand Up @@ -139,6 +141,8 @@ public AndConstraint<TypeSelectorAssertions> BeDecoratedWithOrInherit<TAttribute
Expression<Func<TAttribute, bool>> isMatchingAttributePredicate, string because = "", params object[] becauseArgs)
where TAttribute : Attribute
{
Guard.ThrowIfArgumentIsNull(isMatchingAttributePredicate, nameof(isMatchingAttributePredicate));

Type[] typesWithoutMatchingAttribute = Subject
.Where(type => !type.HasMatchingAttribute(isMatchingAttributePredicate, true))
.ToArray();
Expand Down Expand Up @@ -203,6 +207,8 @@ public AndConstraint<TypeSelectorAssertions> NotBeDecoratedWith<TAttribute>(stri
Expression<Func<TAttribute, bool>> isMatchingAttributePredicate, string because = "", params object[] becauseArgs)
where TAttribute : Attribute
{
Guard.ThrowIfArgumentIsNull(isMatchingAttributePredicate, nameof(isMatchingAttributePredicate));

Type[] typesWithMatchingAttribute = Subject
.Where(type => type.HasMatchingAttribute(isMatchingAttributePredicate))
.ToArray();
Expand Down Expand Up @@ -267,6 +273,8 @@ public AndConstraint<TypeSelectorAssertions> NotBeDecoratedWithOrInherit<TAttrib
Expression<Func<TAttribute, bool>> isMatchingAttributePredicate, string because = "", params object[] becauseArgs)
where TAttribute : Attribute
{
Guard.ThrowIfArgumentIsNull(isMatchingAttributePredicate, nameof(isMatchingAttributePredicate));

Type[] typesWithMatchingAttribute = Subject
.Where(type => type.HasMatchingAttribute(isMatchingAttributePredicate, true))
.ToArray();
Expand Down
46 changes: 46 additions & 0 deletions Tests/Shared.Specs/BasicEquivalencySpecs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2566,6 +2566,29 @@ public void When_an_int_is_compared_equivalent_to_a_string_representing_the_numb
act.Should().NotThrow();
}

[Fact]
public void When_injecting_a_null_predicate_into_WithAutoConversionFor_it_should_throw()
{
//-----------------------------------------------------------------------------------------------------------
// Arrange
//-----------------------------------------------------------------------------------------------------------
var subject = new object();

var expectation = new object();

//-----------------------------------------------------------------------------------------------------------
// Act
//-----------------------------------------------------------------------------------------------------------
Action act = () => subject.Should().BeEquivalentTo(expectation,
options => options.WithAutoConversionFor(predicate: null));

//-----------------------------------------------------------------------------------------------------------
// Assert
//-----------------------------------------------------------------------------------------------------------
act.Should().ThrowExactly<ArgumentNullException>()
.Which.ParamName.Should().Be("predicate");
}

[Fact]
public void When_only_a_single_property_is_and_can_be_converted_but_the_other_one_doesnt_match_it_should_throw()
{
Expand Down Expand Up @@ -2626,6 +2649,29 @@ public void When_only_a_single_property_is_converted_and_the_other_matches_it_sh
act.Should().NotThrow();
}

[Fact]
public void When_injecting_a_null_predicate_into_WithoutAutoConversionFor_it_should_throw()
{
//-----------------------------------------------------------------------------------------------------------
// Arrange
//-----------------------------------------------------------------------------------------------------------
var subject = new object();

var expectation = new object();

//-----------------------------------------------------------------------------------------------------------
// Act
//-----------------------------------------------------------------------------------------------------------
Action act = () => subject.Should().BeEquivalentTo(expectation,
options => options.WithoutAutoConversionFor(predicate: null));

//-----------------------------------------------------------------------------------------------------------
// Assert
//-----------------------------------------------------------------------------------------------------------
act.Should().ThrowExactly<ArgumentNullException>()
.Which.ParamName.Should().Be("predicate");
}

[Fact]
public void When_a_specific_mismatching_property_is_excluded_from_conversion_it_should_throw()
{
Expand Down
20 changes: 20 additions & 0 deletions Tests/Shared.Specs/CollectionAssertionSpecs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2982,6 +2982,26 @@ public void When_collection_contains_an_unexpected_item_it_should_throw()
"Expected collection {1, 2, 3} to not contain element 1 because we don't like it, but found it anyhow.");
}

[Fact]
public void When_injecting_a_null_predicate_into_NotContain_it_should_throw()
{
//-----------------------------------------------------------------------------------------------------------
// Arrange
//-----------------------------------------------------------------------------------------------------------
IEnumerable<int> collection = new int[] { };

//-----------------------------------------------------------------------------------------------------------
// Act
//-----------------------------------------------------------------------------------------------------------
Action act = () => collection.Should().NotContain(predicate: null);

//-----------------------------------------------------------------------------------------------------------
// Assert
//-----------------------------------------------------------------------------------------------------------
act.Should().ThrowExactly<ArgumentNullException>()
.Which.ParamName.Should().Be("predicate");
}

[Fact]
public void When_collection_does_contain_an_unexpected_item_matching_a_predicate_it_should_throw()
{
Expand Down

0 comments on commit 9197d21

Please sign in to comment.