【问题标题】:Using Expression Trees as an argument constraint使用表达式树作为参数约束
【发布时间】:2015-05-24 22:56:13
【问题描述】:

我可以在 FakeIteasy CallTo 断言中使用表达式树作为参数约束吗?

给定具有以下签名的接口上的方法:

interface IRepository<TEntity>
{
    TEntity Single(Expression<Func<TEntity, bool>> predicate);

在这样的代码中被调用:

Flight flight = repository.Single(f => f.ID == id);

我想到一个单元测试做这样的事情:

Expression<Func<Flight, bool>> myExpression = flight => flight.ID == 1;

A.CallTo(() => repository.Single(
                  A<Expression<Func<Flight, bool>>>.That.Matches(myExpression)))
                  .Returns(new Flight());

但这会产生警告:尝试明确指定类型参数

我目前不得不使用不理想的 Ignored 属性。

【问题讨论】:

    标签: fakeiteasy


    【解决方案1】:

    “匹配”方法采用 lambda,但您试图将表达式传递给它。你想用“匹配”电话说什么?你在平等上匹配吗?在这种情况下,您只需编写:

    A.CallTo(() => repository.Single(myExpression)).Returns(new Flight());

    如果您想将表达式限制在其他东西上,您必须将以下类型的谓词:Func&lt;Expression&lt;Func&lt;Flight, bool&gt;&gt;, bool&gt; 传递给“匹配”方法。

    【讨论】:

    • 我先尝试了这个,但电话从未被拦截,假的 strict 证实了这一点。尝试匹配(a => a == myExpression),也没有被拦截。
    • 你想要的是平等匹配?表达式如何从测试传递到存储库?在大多数情况下(不是全部)匹配相等的表达式似乎有点奇怪,在大多数情况下,您想要的是一个确定传入表达式是否相等而不相等的测试。你说这个电话根本没有被拦截,换句话说,如果你做假的严格它不会中断?
    • 嗨@Patric,使假严格会引发ExpectationException(对未配置假的调用)。我要拦截的存储库上的调用看起来像这个 repository.Single(f => f.ID == id) 我想在变量 id 为 1 时拦截它。
    • 那么你不能使用相等比较,你必须传递一个匹配谓词来检查表达式。 Expression-type 的 Equals-method 不这样做,它测试实例相等性。
    【解决方案2】:

    谢谢帕特里克,

    检查表达式正是我需要做的,即解析表达式 (f => f.ID == id) 并执行 == 的右侧以获取其运行时值.

    在代码中是这样的:

    A.CallTo(() => flightRepository.Single(A<Expression<Func<Flight, bool>>>.That
                    .Matches(exp => Expression.Lambda<Func<int>>(((BinaryExpression)exp.Body).Right).Compile().Invoke() == 1)))
                    .Returns(new Flight());
    

    但是我不禁想到,必须有一种更优雅的方式来达到同样的目的。不过我会把它留到另一天。

    再次感谢, 迈克尔·麦克道尔

    【讨论】:

    • 我认为更优雅的解决方案是依赖于 IRepository 之外的另一个抽象,例如,这可能是 IFlightRepository(但也有其他引人注目的替代方案)。如果 IFlightRepository 公开了一个方法“GetFlightById”,那么测试它不会有任何问题。另一方面,飞行存储库的实现可以很好地依赖于 IRepository 这非常接近金属并且不需要单元测试,但应该由集成测试覆盖。不过,这只是我个人的看法。
    • 是的,definitlay 需要另一个层来抽象我的 EntLib 数据访问代码。不过,我目前正在努力解决许多不同的解决方案。任何指针将不胜感激。
    【解决方案3】:

    我在尝试将表达式断言为参数时遇到了同样的问题,但我使用的是 Moq。该解决方案也应该对您有用...

    我将大部分功劳归功于对类似问题的回答: Moq Expect On IRepository Passing Expression

    它基本上说您可以对表达式执行 ToString() 并比较它们。这有点老套,但只有一个缺点; lambda 表达式中的变量名称必须匹配。

    这是一个例子......

        [Test]
        public void TestWhichComparesExpressions()
        {
            // setup
            _mockRepository.Setup(x => x.GetByFilter(MatchQuery())).Returns(new List<Record>());
    
            // execute
            var records = _service.GetRecordsByFilter();
    
            // assert
            Assert.IsNotNull(records);
            Assert.AreEqual(0, records.Count());
        }
    
        private static Expression<Func<DomainRecord, bool>> MatchQuery()
        {
            return MatchExpression(ServiceClass.QueryForTheRecords); // constant
        }
    
        // https://stackoverflow.com/questions/288413/moq-expect-on-irepository-passing-expression/1120836#1120836
        private static Expression<Func<DomainRecord, bool>> MatchExpression(Expression<Func<DomainRecord, bool>> expression)
        {
            return It.Is<Expression<Func<DomainRecord, bool>>>(e => e.ToString() == expression.ToString());
        }
    

    我决定将表达式放入使用它的类的常量中,以保证如果有人更改了 lambda 表达式的变量名,它在测试中将是相同的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-05-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多