2

Below sample code is working fine in production, but cannot be unit tested because the EntityFunctions.

my unit test project is using InMemoryDatabase instead of real SQL database. I can easily solve my problem by creating a View in SQL database with computed column myValue and newValue. I like to find a way to do the unit test work without changing my method and without creating new SQL view


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.

Community
  • 1
  • 1
  • What is your question? – Eric J. Feb 14 '13 at 20:38
  • title is my question ! How to unit test the method GetNewValues() – user2073400 Feb 14 '13 at 20:43
  • What exactly would you gain from testing this? – Ryan Gates Feb 14 '13 at 21:09
  • if you have done Unit Test by using FakeDataBase, you will know the unit test for method GetNewvalue() will fail with message such as EntityFunctions is only supported in LINQ to Entity. For LINQ to Object, you need to use bottom two lines. Our job is to create Unit Test for every business logic method. Sample code is used for displaying my question easily. – user2073400 Feb 14 '13 at 21:22

3 Answers3

5

Unless I am mistaken, you are going to switch the implementation of the EcaseReferralCases with another IQueryable, probably a LINQ To Objects queryable source.

The most robust way would probably be to use an expression visitor to replace calls to EntityFunctions with your own, L2Objects compatible functions.

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;
  • Production implementations are still guaranteed to raise exceptions if not running on the database;
  • The queries can be tested against a fake repository;
Jean Hominal
  • 16,518
  • 5
  • 56
  • 90
  • Thank you very much. I will try it in my real project. – user2073400 Feb 15 '13 at 13:22
  • Please note that the `TExpressionVisitor` parameter in the QueryProvider may a case of overengineering, depending on your needs. – Jean Hominal Feb 15 '13 at 14:50
  • I failed to implement your approach in my project. I have difficulty to setup the linq2ObjectsSource, visitedSource and visitedQuery in my case. – user2073400 Feb 25 '13 at 14:19
  • A million votes to you! That was awesome and solved my problem so elegantly! Awesome job! – flipchart Oct 18 '13 at 08:07
  • 1
    This is an excellent answer, thank you. To add support for `OrderBy` queries, make `VisitedQueryable` implement `IOrderedQueryable` instead of `IQueryable`, like so: `public class VisitedQueryable : IOrderedQueryable where TExpressionVisitor : ExpressionVisitor, new()` (no other change necessary; `IOrderedQueryable` is an empty interface which inherits from `IQueryable`). – label17 Nov 07 '14 at 03:25
1

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 did the same in: from o in myContext.myEntities Where (myExpression) select o.startDate. but now, my question : from o in myContext.myEntities Select EntityFunctions.AddDays(o.startDate, 30). This time, EntityFunctions.AddDays is in selected column, not in where statement – user2073400 Feb 15 '13 at 00:52
  • Hi, 500 - Internal Server Error; I do like your suggestion, but I don't know how to implement the customerized part of select statement in my query. Can you provide more detail information? Thanks – user2073400 Feb 19 '13 at 19:44
  • @500 wouldn't that cause the dreaded "unrecognized method" exception when called from a Linq to Entities expression ? – guillaume31 Apr 08 '13 at 15:35
0

I do like to implement ExpressionVisitor as Jean Hominal recommended. My difficulty is how to define the linq2ObjectsSource, visitedSource and visitedQuery in my case. 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). 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:

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

}