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

主宰稳场 提交于 2019-11-28 01:33:38

问题


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...


回答1:


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.




回答2:


Sorry I should have answered this earlier. It is true that Blair Conrad and I had a chat and he helped me understand how to test the predicates better. Based on his recommendation I came up with the following solution.

In my tests I created a helper Expression extractor show below:

private static string ExpressionExtractor(Expression<Func<CrossReferenceRelationshipEF, bool>> predicate)
{
    var expression = ((BinaryExpression) ((LambdaExpression) ((MethodCallExpression) predicate.Body).Arguments[1]).Body);
    var value = Expression.Lambda<Func<object>>(Expression.Convert(expression.Right, typeof (object))).Compile().Invoke();

    return value.ToString();
}

And then in my tests I could do my assert like this:

//Assert        
A.CallTo(() => crossReferenceRelationshipRepositoryMock.SearchFor(A<Expression<Func<CrossReferenceRelationshipEF, bool>>>.That
    .Matches(exp => ExpressionExtractor(exp) == "20/01/2014 14:06:55")))
    .MustHaveHappened(Repeated.Exactly.Twice);


来源:https://stackoverflow.com/questions/21437044/how-to-test-for-a-match-with-fakeiteasy-on-a-predicate-call

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!