0Day Forums
How to Unit Test GetNewValues() which contains EntityFunctions.AddDays function - Printable Version

+- 0Day Forums (https://zeroday.vip)
+-- Forum: Coding (https://zeroday.vip/Forum-Coding)
+--- Forum: FrameWork (https://zeroday.vip/Forum-FrameWork)
+--- Thread: How to Unit Test GetNewValues() which contains EntityFunctions.AddDays function (/Thread-How-to-Unit-Test-GetNewValues-which-contains-EntityFunctions-AddDays-function)



How to Unit Test GetNewValues() which contains EntityFunctions.AddDays function - marjetvocizb - 07-20-2023

> 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][1], I used that approach to solve one of my unit test difficulty, but don't know how to solve this problem.


[1]:

[To see links please register here]




RE: How to Unit Test GetNewValues() which contains EntityFunctions.AddDays function - mesropian933819 - 07-20-2023

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.


RE: How to Unit Test GetNewValues() which contains EntityFunctions.AddDays function - rhondarhondda927 - 07-20-2023

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<myType> GetSelectQuery(IQuerable<EcaseReferralCase> query), then have corresponding class in Production and Test project which is derived from that interface and have implementation of GetSelectQuery(IQuerable<EcaseReferralCase> 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);

}






RE: How to Unit Test GetNewValues() which contains EntityFunctions.AddDays function - monodromywslryoy - 07-20-2023

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;