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