【问题标题】:How to Unit Test GetNewValues() which contains EntityFunctions.AddDays function如何对包含 EntityFunctions.AddDays 函数的 GetNewValues() 进行单元测试
【发布时间】:2013-02-14 20:21:33
【问题描述】:

以下示例代码在生产中运行良好,但不能为单位 已测试,因为 EntityFunctions。

我的单元测试项目正在使用 InMemoryDatabase 而不是真正的 SQL 数据库。我可以轻松解决我的 通过在 SQL 数据库中使用计算列创建视图来解决问题 myValue 和 newValue。我喜欢找到一种方法来做单元测试工作 无需更改我的方法,也无需创建新的 SQL 视图


public class EcaseReferralCaseRepository : Repository
{

        public class myType
        {
                public DateTime myValue;
                public DateTime newValue;
        }

        public myType GetNewValues()
        {
                return 
                        (myType)(from o in context.EcaseReferralCases
                        select new myType
                        {
                            // LINQ to Entity
                            myValue = (DateTime)System.Data.Objects.EntityFunctions.AddDays(o.StartDate, 0),
                            newValue = (DateTime)System.Data.Objects.EntityFunctions.AddDays(o.StartDate, 30)

                            // LINQ to Object
                            //myValue = o.StartDate.AddDays(0),
                            //newValue = o.StartDate.AddDays(30)

                        });
        }
}

This link shows a good example to unit test EntityFunctions,我用这种方法解决了我的一个单元测试困难,但不知道如何解决这个问题。

【问题讨论】:

  • 你的问题是什么?
  • 标题是我的问题!如何对 GetNewValues() 方法进行单元测试
  • 通过测试你能得到什么?
  • 如果您使用 FakeDataBase 完成了单元测试,您将知道 GetNewvalue() 方法的单元测试将失败,并显示诸如 EntityFunctions 仅在 LINQ to Entity 中支持的消息。对于 LINQ to Object,您需要使用底部两行。我们的工作是为每个业务逻辑方法创建单元测试。示例代码用于轻松显示我的问题。

标签: c# unit-testing frameworks entity


【解决方案1】:

除非我弄错了,否则您将使用另一个 IQueryable 切换 EcaseReferralCases 的实现,这可能是一个 LINQ To Objects 可查询源。

最可靠的方法可能是使用表达式访问者来用您自己的 L2Objects 兼容函数替换对 EntityFunctions 的调用。

这是我的实现:

using System;
using System.Data.Objects;
using System.Linq;
using System.Linq.Expressions;

static class EntityFunctionsFake
{
    public static DateTime? AddDays(DateTime? original, int? numberOfDays)
    {
        if (!original.HasValue || !numberOfDays.HasValue)
        {
            return null;
        }
        return original.Value.AddDays(numberOfDays.Value);
    }
}
public class EntityFunctionsFakerVisitor : ExpressionVisitor
{
    protected override Expression VisitMethodCall(MethodCallExpression node)
    {
        if (node.Method.DeclaringType == typeof(EntityFunctions))
        {
            var visitedArguments = Visit(node.Arguments).ToArray();
            return Expression.Call(typeof(EntityFunctionsFake), node.Method.Name, node.Method.GetGenericArguments(), visitedArguments);
        }

        return base.VisitMethodCall(node);
    }
}
class VisitedQueryProvider<TVisitor> : IQueryProvider
    where TVisitor : ExpressionVisitor, new()
{
    private readonly IQueryProvider _underlyingQueryProvider;
    public VisitedQueryProvider(IQueryProvider underlyingQueryProvider)
    {
        if (underlyingQueryProvider == null) throw new ArgumentNullException();
        _underlyingQueryProvider = underlyingQueryProvider;
    }

    private static Expression Visit(Expression expression)
    {
        return new TVisitor().Visit(expression);
    }

    public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
    {
        return new VisitedQueryable<TElement, TVisitor>(_underlyingQueryProvider.CreateQuery<TElement>(Visit(expression)));
    }

    public IQueryable CreateQuery(Expression expression)
    {
        var sourceQueryable = _underlyingQueryProvider.CreateQuery(Visit(expression));
        var visitedQueryableType = typeof(VisitedQueryable<,>).MakeGenericType(
            sourceQueryable.ElementType,
            typeof(TVisitor)
            );

        return (IQueryable)Activator.CreateInstance(visitedQueryableType, sourceQueryable);
    }

    public TResult Execute<TResult>(Expression expression)
    {
        return _underlyingQueryProvider.Execute<TResult>(Visit(expression));
    }

    public object Execute(Expression expression)
    {
        return _underlyingQueryProvider.Execute(Visit(expression));
    }
}
public class VisitedQueryable<T, TExpressionVisitor> : IOrderedQueryable<T>
    where TExpressionVisitor : ExpressionVisitor, new()
{
    private readonly IQueryable<T> _underlyingQuery;
    private readonly VisitedQueryProvider<TExpressionVisitor> _queryProviderWrapper;
    public VisitedQueryable(IQueryable<T> underlyingQuery)
    {
        _underlyingQuery = underlyingQuery;
        _queryProviderWrapper = new VisitedQueryProvider<TExpressionVisitor>(underlyingQuery.Provider);
    }

    public IEnumerator<T> GetEnumerator()
    {
        return _underlyingQuery.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public Expression Expression
    {
        get { return _underlyingQuery.Expression; }
    }

    public Type ElementType
    {
        get { return _underlyingQuery.ElementType; }
    }

    public IQueryProvider Provider
    {
        get { return _queryProviderWrapper; }
    }
}

这是一个使用示例:

var linq2ObjectsSource = new List<DateTime?>() { null }.AsQueryable();
var visitedSource = new VisitedQueryable<DateTime?, EntityFunctionsFakerVisitor>(linq2ObjectsSource);
var visitedQuery = visitedSource.Select(dt => EntityFunctions.AddDays(dt, 1));
var results = visitedQuery.ToList();
Assert.AreEqual(1, results.Count);
Assert.AreEqual(null, results[0]);

这样,您可以获得所有理想的特征:

  • 开发者可以继续使用Entity Framework定义的标准EntityFunctions
  • 如果不在数据库上运行,生产实现仍然可以保证引发异常;
  • 可以针对虚假存储库测试查询;

【讨论】:

  • 非常感谢。我会在我的实际项目中尝试一下。
  • 请注意,QueryProvider 中的TExpressionVisitor 参数可能是过度设计的情况,具体取决于您的需要。
  • 我未能在我的项目中实施您的方法。在我的情况下,我很难设置 linq2ObjectsSource、visitedSource 和visitedQuery。
  • 一百万票给你!这太棒了,如此优雅地解决了我的问题!很棒的工作!
  • 这是一个很好的答案,谢谢。要添加对OrderBy 查询的支持,使VisitedQueryable 实现IOrderedQueryable&lt;T&gt; 而不是IQueryable&lt;T&gt;,如下所示:public class VisitedQueryable&lt;T, TExpressionVisitor&gt; : IOrderedQueryable&lt;T&gt; where TExpressionVisitor : ExpressionVisitor, new()(无需其他更改;IOrderedQueryable&lt;T&gt; 是一个空接口,继承自IQueryable&lt;T&gt;)。
【解决方案2】:

而不是打电话

System.Data.Objects.EntityFunctions.AddDays

直接,我会注入一个自定义接口,它将调用转发给该方法,但随后可以模拟以进行测试。

【讨论】:

  • 我做了同样的事情:from o in myContext.myEntities Where (myExpression) select o.startDate。但现在,我的问题是:从 myContext.myEntities 中的 o 选择 EntityFunctions.AddDays(o.startDate, 30)。这一次,EntityFunctions.AddDays 在选中的列中,而不是在 where 语句中
  • 嗨,500 - 内部服务器错误;我确实喜欢你的建议,但我不知道如何在我的查询中实现 select 语句的定制部分。你能提供更详细的信息吗?谢谢
  • @500 从 Linq to Entities 表达式调用时不会导致可怕的“无法识别的方法”异常吗?
【解决方案3】:

我确实喜欢按照 Jean Hominal 的建议实施 ExpressionVisitor。我的困难是如何在我的例子中定义 linq2ObjectsSource、visitedSource 和visitedQuery。所以最后,我只是为方法 IQuerable GetSelectQuery(IQuerable query) 创建一个接口,然后在生产和测试项目中具有相应的类,该类派生自该接口并具有 GetSelectQuery(IQuerable 查询)的实现。它工作正常。

public interface IEntityFunctionsExpressions
{
   IQuerable<myType> GetSelectQuery(IQuerable<EcaseReferralCase> query); 
}

在生产项目中:

public class EntityFunctionsExpressions : IEntityFunctionsExpressions
{
    public EntityFunctionsExpressions()
    {
    }

    public IQuerable<myType> GetSelectQuery(IQuerable<EcaseReferralCase> query)
    {
        // Expression for LINQ to Entities, does not work with LINQ to Objects
        return 
                    (myType)(from o in query
                    select new myType
                    {
                        // LINQ to Entity
                        myValue = (DateTime)System.Data.Objects.EntityFunctions.AddDays(o.StartDate, 0),
                        newValue = (DateTime)System.Data.Objects.EntityFunctions.AddDays(o.StartDate, 30)

                    });
    }
}

在单元测试项目中:

public class MockEntityFunctionsExpressions : IEntityFunctionsExpressions
{
    public MockEntityFunctionsExpressions()
    {
    }

    public IQuerable<myType> GetSelectQuery(IQuerable<EcaseReferralCase> query)
    {
        // Expression for LINQ to Objects, does not work with LINQ to Entities
        return 
                    (myType)(from o in query
                    select new myType
                    {
                        // LINQ to Object
                        myValue = o.StartDate.AddDays(0),
                        newValue = o.StartDate.AddDays(30)
                    });
    }
}

然后重写GetNewValues()方法:

public myType GetNewValues() { return myrepository.EntityFunctionsExpressions.GetSelectQuery(context.EcaseReferralCases);

}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-19
    • 2015-02-28
    • 2014-02-08
    • 2020-11-13
    • 2021-10-20
    相关资源
    最近更新 更多