2013-02-14 52 views
2

下面的示例代码工作在制作精细,但也不能因为EntityFunctions测试单元 单元测试GetNewValues()。如何包含EntityFunctions.AddDays功能

我的单元测试项目使用的是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,我用这种方法来解决我单位的一个测试困难,但不知道如何解决这个问题。

+0

你的问题是什么? – 2013-02-14 20:38:42

+0

标题是我的问题!如何对方法进行单元测试GetNewValues() – user2073400 2013-02-14 20:43:57

+0

从测试中您将获得什么? – 2013-02-14 21:09:57

回答

1

比呼叫

System.Data.Objects.EntityFunctions.AddDays 

直接相反,我将注入的自定义接口,该接口将呼叫转发到该方法,但其然后可以被模拟用于测试目的。

+0

我做了同样的:从myContext.myEntities中的o where(myExpression)选择o.startDate。但现在,我的问题:从myContext.myEntities中的o选择EntityFunctions.AddDays(o.startDate,30)。这次,EntityFunctions.AddDays在选定的列中,而不是在where语句中 – user2073400 2013-02-15 00:52:09

+0

Hi,500 - 内部服务器错误;我喜欢你的建议,但我不知道如何在我的查询中实现select语句的customized部分。你能提供更多的细节信息吗?谢谢 – user2073400 2013-02-19 19:44:15

+0

@ 500从Linq to Entities表达式调用时不会导致可怕的“无法识别的方法”异常? – guillaume31 2013-04-08 15:35:51

5

除非我误解了,否则您将切换实施EcaseReferralCases与另一个IQueryable,可能是一个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]); 

这样一来,你的所有期望的特性:

  • 开发人员可以继续使用实体框架定义的标准EntityFunctions;
  • 如果不在数据库上运行,生产实现仍然保证会引发异常;
  • 查询可以针对伪造的存储库进行测试;
+0

非常感谢。我会在我的真实项目中尝试。 – user2073400 2013-02-15 13:22:46

+0

请注意,QueryProvider中的'TExpressionVisitor'参数可能是一个过度工程的情况,这取决于您的需求。 – 2013-02-15 14:50:35

+0

我未能在我的项目中实施您的方法。在我的情况下,我很难设置linq2ObjectsSource,visitedSource和visitedQuery。 – user2073400 2013-02-25 14:19:04

0

我确实喜欢像Jean Hominal推荐的那样实现ExpressionVisitor。我的困难是如何在我的情况下定义linq2ObjectsSource,visitedSource和visitedQuery。所以最后,我只是为方法IQuerable GetSelectQuery(IQuerable query)创建一个接口,然后在Production和Test项目中有相应的类,该项目从该接口派生并具有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) 

        }); 
    } 
} 
在单元测试项目

​​

然后重写GetNewValues()方法:

公共的myType GetNewValues() { 返回myrepository.EntityFunctionsExpressions.GetSelec TQuery的(context.EcaseReferralCases);

}