【问题标题】:How to test for a Match with FakeItEasy on a predicate call?如何在谓词调用中测试与 FakeItEasy 的匹配?
【发布时间】:2014-02-21 14:35:32
【问题描述】:

我的代码中有以下调用:

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

我不确定我做错了什么......

【问题讨论】:

    标签: c# lambda predicate fakeiteasy


    【解决方案1】:

    对不起,我应该早点回答这个问题。确实,Blair Conrad 和我聊过,他帮助我了解了如何更好地测试谓词。根据他的建议,我想出了以下解决方案。

    在我的测试中,我创建了一个辅助表达式提取器,如下所示:

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

    【讨论】:

      【解决方案2】:

      披露 - VasilisP 和我昨天聊了一点。

      在某种程度上,这并不是一个真正的 FakeItEasy 问题。您在 A.CallTo 调用中设置参数匹配器的方法是合理的。问题是您提供的用于匹配谓词的 lambda 不起作用。这将问题归结为“我如何判断一个表达式是否是我想要的?”。

      还有其他 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
      }
      

      不过,总的来说,我会警告不要进行这样的测试 - 这对我来说似乎有点脆弱。当然,您可能有充分的理由采用这种方法。但是,如果它适合您,另一种方法可能是捕获谓词,然后通过使其与已知的候选对象列表运行来询问它。如果它以您想要的方式过滤,那么它就会通过。 这样,如果有人以仍然有效的方式更改传入的谓词,也许通过将运算符切换到日期在左边的&lt;,测试仍然有效。我只是把它作为另一种选择扔掉。

      【讨论】:

      • 感谢您的帮助。我在下面发布了我的工作解决方案。对不起,我花了很长时间。
      猜你喜欢
      • 2022-10-09
      • 1970-01-01
      • 1970-01-01
      • 2012-03-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多