Как проверить совпадение с FakeItEasy при вызове предиката?

У меня есть следующий вызов в моем коде:

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));

и я пытаюсь использовать FakeItEasy, чтобы убедиться, что когда dateTimeFilter имеет значение, SearchFor(…) вызывается в моем репозитории с правильной функцией.

Итак, мой тест выглядит примерно так:

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);

Что не правильно. Как можно проверить, вызываю ли я SearchFor(…) с правильным выражением?

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

Фактическое значение, передаваемое в SearchFor(…), равно DateTime.MinValue, поэтому я изменил свое утверждение на:

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);

который терпит неудачу, и исключение, которое я получаю,

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

и я не уверен, что я делаю неправильно...


person VasilisP    schedule 29.01.2014    source источник


Ответы (2)


Раскрытие информации. Вчера мы с VasilisP немного поболтали об этом.

В некотором смысле это не проблема FakeItEasy. Ваш подход к настройке средства сопоставления аргументов в вызове A.CallTo является правильным. Проблема в том, что лямбда, которую вы предоставили для соответствия предикату, не работает. Это сводит вопрос к «как я могу определить, является ли выражение тем, чем я хочу его видеть?».

Есть и другие вопросы StackOverflow, которые задают вопросы, подобные этому, например

Один из этих подходов может сработать для вас.

Однако непосредственной причиной исключения, которое вы видите, является то, что переданный предикат является не BinaryExpression, а MethodCallExpression. Вы можете подумать об изменении своего теста, чтобы учесть это, и следовать по пути, по которому он вас ведет.

Я заполнил некоторые определения классов и извлек сопоставление с этой функцией и смог, по крайней мере, найти dateArgument в предикате:

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
}

В общем, я бы предостерег от такого тестирования - мне оно кажется немного хрупким. Конечно, у вас может быть веская причина для такого подхода. Но если вас это устраивает, другой способ может заключаться в том, чтобы просто захватить предикат, а затем опросить его, запустив его для известного списка объектов-кандидатов. Если он фильтрует так, как вы хотите, то он проходит. Таким образом, если кто-то изменит переданный предикат так, чтобы он все еще работал, например, переключив оператор на < с датой слева, тест все равно будет работать. Я просто отбрасываю это как еще один вариант.

person Blair Conrad    schedule 29.01.2014
comment
Спасибо за вашу помощь. Я разместил свое рабочее решение ниже. Извините, это заняло у меня много времени. - person VasilisP; 22.04.2014

Извините, я должен был ответить на это раньше. Это правда, что мы с Блэром Конрадом поболтали, и он помог мне понять, как лучше тестировать предикаты. Основываясь на его рекомендации, я придумал следующее решение.

В своих тестах я создал вспомогательный экстрактор Expression, показанный ниже:

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();
}

И тогда в моих тестах я мог бы сделать свое утверждение следующим образом:

//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);
person VasilisP    schedule 22.04.2014