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