Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test for a Match with FakeItEasy on a predicate call?

I have the following call in my code:

var dbResults = new List<CrossReferenceRelationshipEF>();
dbResults = dateTimeFilter == null
    ? new List<CrossReferenceRelationshipEF>(
        CrossReferenceRelationshipRepository.GetAll()
                .ToList().OrderBy(crr => crr.ToPartner))
    : new List<CrossReferenceRelationshipEF>(
        CrossReferenceRelationshipRepository.SearchFor(
            crr => crr.HistoricEntries
                .Any(he => he.ModifiedDatetime > dateTimeFilter))
                .ToList().OrderBy(crr => crr.ToPartner));

and I am trying to use FakeItEasy to verify that when the dateTimeFilter has a value, the SearchFor(…) is being called within my repository with the correct Function.

So my test looks something like this:

A.CallTo(() => crossReferenceRelationshipRepositoryMock.SearchFor(A<Expression<Func<CrossReferenceRelationshipEF,bool>>>.That
    .Matches(exp => Expression.Lambda<Func<DateTime>>(((BinaryExpression)exp.Body).Right).Compile().Invoke() == filterByDate)))
    .MustHaveHappened(Repeated.Exactly.Once);

Which is not correct. What would be a way to test the whether or not I am calling SearchFor(…) with the correct expression?

crr => crr.HistoricEntries.Any(he => he.ModifiedDatetime > dateTimeFilter)

The actual value being passed into SearchFor(…) is DateTime.MinValue so I changed my assertion to:

A.CallTo(() => crossReferenceRelationshipRepositoryMock.SearchFor(A<Expression<Func<CrossReferenceRelationshipEF, bool>>>.That
    .Matches(exp => Expression.Lambda<Func<DateTime>>(((BinaryExpression)exp.Body).Right).Compile().Invoke() == DateTime.MinValue)))
    .MustHaveHappened(Repeated.Exactly.Once);

which is failing and the exception I am getting is

System.InvalidCastException:
  Unable to cast object of type 'System.Linq.Expressions.MethodCallExpressionN'
  to type 'System.Linq.Expressions.BinaryExpression'.

and I am not sure what I am doing wrong...

like image 228
VasilisP Avatar asked Jan 29 '14 16:01

VasilisP


1 Answers

Disclosure - VasilisP and I chatted a little about this yesterday.

In a way, this isn't really a FakeItEasy problem. Your approach for setting up an argument matcher within an A.CallTo call is sound. The problem is that the lambda you supplied to match the predicate is not working. This brings the question down to the "how can I tell if an expression is what I want it to be?".

There are other StackOverflow questions that ask questions similar to this, such as

  • Most efficient way to test equality of lambda expressions,
  • How to check if two Expression<Func<T, bool>> are the same, and
  • How to test expressions equality

One of those approaches may work for you.

However, the immediate cause of the exception you see is that the passed-in predicate isn't a BinaryExpression, it's a MethodCallExpression. You could consider altering your test to account for that and follow the path where it leads you.

I filled in some class definitions and extracted the matcher to this function and was able to at least locate the dateArgument in the predicate:

public bool IsPredicateGood(Expression<Func<CrossReferenceRelationshipEF, bool>> predicate)
{
    var typedPredicate = (MethodCallExpression) predicate.Body;
    var innerPredicate = ((LambdaExpression)typedPredicate.Arguments[1]).Body;
    var dateArgument = ((BinaryExpression) innerPredicate).Right;
    return dateArgument != null; // not a real test yet, but you could adapt
}

In general, though, I'd warn against testing quite like this - it seems a little fragile to me. Of course, you may have a good reason for this approach. But if it suits you, another way to go may be to just capture the predicate and then interrogate it by having it run against a known list of candidate objects. If it filters the way you want, then it passes. That way if someone changes the passed-in predicate in a way that would still work, perhaps by switching the operator to a < with the date on the left, the test would still work. I just throw that out as another option.

like image 157
Blair Conrad Avatar answered Sep 20 '22 17:09

Blair Conrad