Как проверить совпадение с 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'.

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

2 ответа

Решение

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

В своих тестах я создал вспомогательное шоу Expression Extractor ниже:

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

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

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

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

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

Однако непосредственной причиной исключения, которое вы видите, является то, что переданный предикат не является 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
}

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

Другие вопросы по тегам