简体   繁体   English

如何对包含EntityFunctions.AddDays函数的GetNewValues()进行单元测试

[英]How to Unit Test GetNewValues() which contains EntityFunctions.AddDays function

Below sample code is working fine in production, but cannot be unit tested because the EntityFunctions. 下面的示例代码在生产中可以正常工作,但是由于EntityFunctions不能进行单元测试。

my unit test project is using InMemoryDatabase instead of real SQL database. 我的单元测试项目使用InMemoryDatabase而不是真实的SQL数据库。 I can easily solve my problem by creating a View in SQL database with computed column myValue and newValue. 通过在SQL数据库中使用计算列myValue和newValue创建一个View,可以轻松解决我的问题。 I like to find a way to do the unit test work without changing my method and without creating new SQL view 我喜欢找到一种方法来进行单元测试,而无需更改方法,也无需创建新的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 , I used that approach to solve one of my unit test difficulty, but don't know how to solve this problem. 该链接显示了单元测试EntityFunctions的一个很好的示例 ,我使用该方法解决了我的单元测试难题之一,但不知道如何解决此问题。

Unless I am mistaken, you are going to switch the implementation of the EcaseReferralCases with another IQueryable , probably a LINQ To Objects queryable source. 除非我没有记错,否则您将使用另一个IQueryable切换EcaseReferralCases的实现,可能是LINQ To Objects可查询源。

The most robust way would probably be to use an expression visitor to replace calls to EntityFunctions with your own, L2Objects compatible functions. 最健壮的方法可能是使用表达式访问器,用您自己的L2Objects兼容函数替换对EntityFunctions调用。

Here is my implementation: 这是我的实现:

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

And here is a usage sample: 这是一个用法示例:

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

In that way, you get all the desirable characteristics: 这样,您将获得所有理想的特性:

  • Developers can continue to use the standard EntityFunctions defined by Entity Framework; 开发人员可以继续使用Entity Framework定义的标准EntityFunctions
  • Production implementations are still guaranteed to raise exceptions if not running on the database; 如果不在数据库上运行,生产实现仍然可以保证引发异常。
  • The queries can be tested against a fake repository; 可以针对伪造的存储库测试查询;

Rather than call 而不是打电话

System.Data.Objects.EntityFunctions.AddDays

directly, I would inject a custom interface, which forwards the call to that method but which can then be mocked for testing purposes. 直接地,我将注入一个自定义接口,该接口将调用转发到该方法,但随后可以对其进行模拟以进行测试。

I do like to implement ExpressionVisitor as Jean Hominal recommended. 我喜欢按照Jean Hominal的建议实施ExpressionVisitor。 My difficulty is how to define the linq2ObjectsSource, visitedSource and visitedQuery in my case. 我的困难是如何在我的情况下定义linq2ObjectsSource,visitedSource和VisitedQuery。 So finally, I just create an Interface for a method IQuerable GetSelectQuery(IQuerable query), then have corresponding class in Production and Test project which is derived from that interface and have implementation of GetSelectQuery(IQuerable query). 所以最后,我只是为方法IQuerable GetSelectQuery(IQuerable query)创建一个接口,然后在Production and Test项目中具有从该接口派生的对应类,并具有GetSelectQuery(IQuerable query)的实现。 It works fine. 工作正常。

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

in production project: 在生产项目中:

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)

                    });
    }
}

in unit test project: 在单元测试项目中:

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

then rewrite GetNewValues() method: 然后重写GetNewValues()方法:

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

} }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM