Ниже образец кода работает нормально в производстве, но не может быть единицей проверены, потому что EntityFunctions.
my unit test используется InMemoryDatabase вместо реальной базы данных SQL. Я могу легко решить мою проблемы, создав представление в базе данных SQL с вычисленным столбцом myValue и newValue. Мне нравится искать способ работы unit testбез изменения моего метода и без создания нового представления 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)
});
}
}
Эта ссылка показывает хороший пример unit test EntityFunctions, я использовал этот подход для решения одной из моих трудностей unit test, но не знаю, как решить эту проблему.
Вместо вызова
System.Data.Objects.EntityFunctions.AddDays
я бы добавил пользовательский интерфейс, который переадресовывает вызов этому методу, но который затем может быть изделен для целей тестирования.
Если я ошибаюсь, вы собираетесь переключить реализацию EcaseReferralCases с помощью другого IQueryable
, возможно, источника запроса LINQ To Objects.
Наиболее надежным способом, вероятно, будет использование посетителя выражения для замены вызовов EntityFunctions
на ваши собственные функции, совместимые с L2Objects.
Вот моя реализация:
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
, определенный Entity Framework;TExpressionVisitor
в QueryProvider может быть причиной чрезмерного повышения квалификации в зависимости от ваших потребностей.
Мне нравится внедрять ExpressionVisitor, как рекомендовал Жан Хоминал. Моя трудность заключается в том, как определить linq2ObjectsSource, visitedSource и visitQuery в моем случае. Итак, наконец, я просто создаю интерфейс для метода IQuerable GetSelectQuery (запрос IQuerable), а затем соответствующий класс в проекте 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)
});
}
}
в проекте unit test:
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)
});
}
}
затем перепишите метод GetNewValues ():
public myType GetNewValues () { return myrepository.EntityFunctionsExpressions.GetSelectQuery(context.EcaseReferralCases);
}