首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >如何在单元测试中使用Moq和DbFunctions来防止NotSupportedException?

如何在单元测试中使用Moq和DbFunctions来防止NotSupportedException?
EN

Stack Overflow用户
提问于 2014-01-28 22:06:29
回答 7查看 10.1K关注 0票数 33

我目前正在尝试对通过实体框架运行的查询运行一些单元测试。查询本身在活动版本上运行没有任何问题,但是单元测试总是失败。

我已经将范围缩小到使用DbFunctions.TruncateTime,但我不知道有什么方法可以让单元测试反映实时服务器上正在发生的事情。

下面是我使用的方法:

代码语言:javascript
复制
    public System.Data.DataTable GetLinkedUsers(int parentUserId)
    {
        var today = DateTime.Now.Date;

        var query = from up in DB.par_UserPlacement
                    where up.MentorId == mentorUserId
                        && DbFunctions.TruncateTime(today) >= DbFunctions.TruncateTime(up.StartDate)
                        && DbFunctions.TruncateTime(today) <= DbFunctions.TruncateTime(up.EndDate)
                    select new
                    {
                        up.UserPlacementId,
                        up.Users.UserId,
                        up.Users.FirstName,
                        up.Users.LastName,
                        up.Placements.PlacementId,
                        up.Placements.PlacementName,
                        up.StartDate,
                        up.EndDate,
                    };

        query = query.OrderBy(up => up.EndDate);

        return this.RunQueryToDataTable(query);
    }

如果我注释掉带有DbFunctions in的行,所有测试都会通过(除了那些检查是否只运行了给定日期的有效结果的测试)。

有没有一种方法可以让我在这些测试中使用DbFunctions.TruncateTime的模拟版本?从本质上讲,它应该只返回Datetime.Date,但这在EF查询中不可用。

编辑:下面是使用日期检查失败的测试:

代码语言:javascript
复制
    [TestMethod]
    public void CanOnlyGetCurrentLinkedUsers()
    {
        var up = new List<par_UserPlacement>
        {
            this.UserPlacementFactory(1, 2, 1), // Create a user placement that is current
            this.UserPlacementFactory(1, 3, 2, false) // Create a user placement that is not current
        }.AsQueryable();

        var set = DLTestHelper.GetMockSet<par_UserPlacement>(up);

        var context = DLTestHelper.Context;
        context.Setup(c => c.par_UserPlacement).Returns(set.Object);

        var getter = DLTestHelper.New<LinqUserGetLinkedUsersForParentUser>(context.Object);

        var output = getter.GetLinkedUsers(1);

        var users = new List<User>();
        output.ProcessDataTable((DataRow row) => students.Add(new UserStudent(row)));

        Assert.AreEqual(1, users.Count);
        Assert.AreEqual(2, users[0].UserId);
    }

编辑2:这是来自相关测试的消息和调试跟踪:

代码语言:javascript
复制
Test Result: Failed

Message: Assert.AreEqual failed. Expected:<1>. Actual:<0>

Debug Trace: This function can only be invoked from LINQ to Entities

据我所知,这是因为没有此方法的LINQ to Entities实现可在此用于单元测试,尽管在活动版本上有(因为它正在查询SQL服务器)。

EN

回答 7

Stack Overflow用户

回答已采纳

发布于 2014-01-31 17:15:40

感谢所有人的帮助,在阅读了qujck提到的垫片后,我设法找到了一个对我有效的解决方案。在添加了一个假的EntityFramework程序集之后,我能够通过将它们更改为以下内容来修复这些测试:

代码语言:javascript
复制
[TestMethod]
public void CanOnlyGetCurrentLinkedUsers()
{
    using (ShimsContext.Create())
    {
        System.Data.Entity.Fakes.ShimDbFunctions.TruncateTimeNullableOfDateTime =
            (DateTime? input) =>
            {
                return input.HasValue ? (DateTime?)input.Value.Date : null;
            };

        var up = new List<par_UserPlacement>
        {
            this.UserPlacementFactory(1, 2, 1), // Create a user placement that is current
            this.UserPlacementFactory(1, 3, 2, false) // Create a user placement that is not current
        }.AsQueryable();

        var set = DLTestHelper.GetMockSet<par_UserPlacement>(up);

        var context = DLTestHelper.Context;
        context.Setup(c => c.par_UserPlacement).Returns(set.Object);

        var getter = DLTestHelper.New<LinqUserGetLinkedUsersForParentUser>(context.Object);

        var output = getter.GetLinkedUsers(1);
    }

    var users = new List<User>();
    output.ProcessDataTable((DataRow row) => users.Add(new User(row)));

    Assert.AreEqual(1, users.Count);
    Assert.AreEqual(2, users[0].UserId);
}
票数 17
EN

Stack Overflow用户

发布于 2017-08-08 01:16:05

我知道我来晚了,但是一个非常简单的解决方法是编写您自己的方法,该方法使用DbFunction属性。然后使用该函数而不是DbFunctions.TruncateTime。

代码语言:javascript
复制
[DbFunction("Edm", "TruncateTime")]
public static DateTime? TruncateTime(DateTime? dateValue)
{
    return dateValue?.Date;
}

当Linq to Entities使用时,使用此函数将执行EDM TruncateTime方法,否则将运行提供的代码。

票数 33
EN

Stack Overflow用户

发布于 2015-02-09 13:48:45

有一个方法可以做到这一点。由于业务逻辑的单元测试通常是鼓励的,而且由于业务逻辑对应用程序数据发出LINQ查询是完全OK的,那么对<>e29>这些LINQ查询进行单元测试必须是完全OK的。

不幸的是,DbFunctions实体框架功能扼杀了我们对包含LINQ查询的代码进行单元测试的能力。此外,在业务逻辑中使用DbFunctions在体系结构上是错误的,因为它将业务逻辑层耦合到特定的持久性技术(这是单独讨论的)。

话虽如此,我们的目标是能够像这样运行LINQ :

代码语言:javascript
复制
var orderIdsByDate = (
    from o in repo.Orders
    group o by o.PlacedAt.Date 
         // here we used DateTime.Date 
         // and **NOT** DbFunctions.TruncateTime
    into g
    orderby g.Key
    select new { Date = g.Key, OrderIds = g.Select(x => x.Id) });

单元测试中,这将归结为对预先安排(例如)的实体的普通数组运行LINQ-to-Objects。在实际运行中,它必须针对实体框架的 real ObjectContext工作。

下面是实现它的的配方-尽管它需要您的几个步骤。我正在删减一个实际的工作示例:

步骤1.将ObjectSet<T>包装在我们自己的IQueryable<T>实现中,以便提供我们自己的IQueryProvider拦截包装器。

代码语言:javascript
复制
public class EntityRepository<T> : IQueryable<T> where T : class
{
    private readonly ObjectSet<T> _objectSet;
    private InterceptingQueryProvider _queryProvider = null;

    public EntityRepository<T>(ObjectSet<T> objectSet)
    {
        _objectSet = objectSet;
    }
    IEnumerator<T> IEnumerable<T>.GetEnumerator()
    {
        return _objectSet.AsEnumerable().GetEnumerator();
    }
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return _objectSet.AsEnumerable().GetEnumerator();
    }
    Type IQueryable.ElementType
    {
        get { return _objectSet.AsQueryable().ElementType; }
    }
    System.Linq.Expressions.Expression IQueryable.Expression
    {
        get { return _objectSet.AsQueryable().Expression; }
    }
    IQueryProvider IQueryable.Provider
    {
        get
        {
            if ( _queryProvider == null )
            {
                _queryProvider = new InterceptingQueryProvider(_objectSet.AsQueryable().Provider);
            }
            return _queryProvider;
        }
    }

    // . . . . . you may want to include Insert(), Update(), and Delete() methods
}

步骤2。实现截取查询提供程序,在我的示例中,它是EntityRepository<T>中的一个嵌套类

代码语言:javascript
复制
private class InterceptingQueryProvider : IQueryProvider
{
    private readonly IQueryProvider _actualQueryProvider;

    public InterceptingQueryProvider(IQueryProvider actualQueryProvider)
    {
        _actualQueryProvider = actualQueryProvider;
    }
    public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
    {
        var specializedExpression = QueryExpressionSpecializer.Specialize(expression);
        return _actualQueryProvider.CreateQuery<TElement>(specializedExpression);
    }
    public IQueryable CreateQuery(Expression expression)
    {
        var specializedExpression = QueryExpressionSpecializer.Specialize(expression);
        return _actualQueryProvider.CreateQuery(specializedExpression);
    }
    public TResult Execute<TResult>(Expression expression)
    {
        return _actualQueryProvider.Execute<TResult>(expression);
    }
    public object Execute(Expression expression)
    {
        return _actualQueryProvider.Execute(expression);
    }
}

步骤3.最后,实现一个名为QueryExpressionSpecializer的帮助器类,它将用DbFunctions.TruncateTime替换DateTime.Date

代码语言:javascript
复制
public static class QueryExpressionSpecializer
{
    private static readonly MethodInfo _s_dbFunctions_TruncateTime_NullableOfDateTime = 
        GetMethodInfo<Expression<Func<DateTime?, DateTime?>>>(d => DbFunctions.TruncateTime(d));

    private static readonly PropertyInfo _s_nullableOfDateTime_Value =
        GetPropertyInfo<Expression<Func<DateTime?, DateTime>>>(d => d.Value);

    public static Expression Specialize(Expression general)
    {
        var visitor = new SpecializingVisitor();
        return visitor.Visit(general);
    }
    private static MethodInfo GetMethodInfo<TLambda>(TLambda lambda) where TLambda : LambdaExpression
    {
        return ((MethodCallExpression)lambda.Body).Method;
    }
    public static PropertyInfo GetPropertyInfo<TLambda>(TLambda lambda) where TLambda : LambdaExpression
    {
        return (PropertyInfo)((MemberExpression)lambda.Body).Member;
    }

    private class SpecializingVisitor : ExpressionVisitor
    {
        protected override Expression VisitMember(MemberExpression node)
        {
            if ( node.Expression.Type == typeof(DateTime?) && node.Member.Name == "Date" )
            {
                return Expression.Call(_s_dbFunctions_TruncateTime_NullableOfDateTime, node.Expression);
            }

            if ( node.Expression.Type == typeof(DateTime) && node.Member.Name == "Date" )
            {
                return Expression.Property(
                    Expression.Call(
                        _s_dbFunctions_TruncateTime_NullableOfDateTime, 
                        Expression.Convert(
                            node.Expression, 
                            typeof(DateTime?)
                        )
                    ),
                    _s_nullableOfDateTime_Value
                );
            }

            return base.VisitMember(node);
        }
    }
}

当然,上面的QueryExpressionSpecializer实现可以泛化为允许插入任意数量的附加转换,允许在LINQ查询中使用自定义类型的成员,即使它们对于实体框架来说是未知的。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/21407748

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档