C# 如何在单元测试中使用Moq和DBFunction来防止NotSupportedException?
我目前正在尝试对通过实体框架运行的查询运行一些单元测试。查询本身在实时版本上运行时没有任何问题,但是单元测试总是失败 我已经将这一点缩小到使用DbFunctions.TruncateTime,但我不知道如何让单元测试反映实时服务器上发生的情况 以下是我正在使用的方法:C# 如何在单元测试中使用Moq和DBFunction来防止NotSupportedException?,c#,entity-framework,unit-testing,moq,C#,Entity Framework,Unit Testing,Moq,我目前正在尝试对通过实体框架运行的查询运行一些单元测试。查询本身在实时版本上运行时没有任何问题,但是单元测试总是失败 我已经将这一点缩小到使用DbFunctions.TruncateTime,但我不知道如何让单元测试反映实时服务器上发生的情况 以下是我正在使用的方法: public System.Data.DataTable GetLinkedUsers(int parentUserId) { var today = DateTime.Now.Date;
public System.Data.DataTable GetLinkedUsers(int parentUserId)
{
var today = DateTime.Now.Date;
var query = from up in DB.par_UserPlacement
where up.MentorId == mentorUserId
&& DbFunctions.TruncateTime(today) >= DbFunctions.TruncateTime(up.StartDate)
&& DbFunctions.TruncateTime(today) <= DbFunctions.TruncateTime(up.EndDate)
select new
{
up.UserPlacementId,
up.Users.UserId,
up.Users.FirstName,
up.Users.LastName,
up.Placements.PlacementId,
up.Placements.PlacementName,
up.StartDate,
up.EndDate,
};
query = query.OrderBy(up => up.EndDate);
return this.RunQueryToDataTable(query);
}
public System.Data.DataTable GetLinkedUsers(int-parentUserId)
{
var today=DateTime.Now.Date;
var query=在DB.par\u UserPlacement中从上到下
其中up.MentorId==mentorUserId
&&DbFunctions.TruncateTime(今天)>=DbFunctions.TruncateTime(up.StartDate)
&&DbFunctions.TruncateTime(今天)up.EndDate);
返回此.RunQueryToDataTable(查询);
}
如果我在中注释掉带有DbFunctions的行,测试都会通过(除了那些检查是否只运行给定日期的有效结果的测试)
有没有办法提供DbFunctions.TruncateTime的模拟版本以用于这些测试?实际上,它应该只是返回Datetime.Date,但在EF查询中不可用
编辑:以下是使用日期检查失败的测试:
[TestMethod]
public void CanOnlyGetCurrentLinkedUsers()
{
var up = new List<par_UserPlacement>
{
this.UserPlacementFactory(1, 2, 1), // Create a user placement that is current
this.UserPlacementFactory(1, 3, 2, false) // Create a user placement that is not current
}.AsQueryable();
var set = DLTestHelper.GetMockSet<par_UserPlacement>(up);
var context = DLTestHelper.Context;
context.Setup(c => c.par_UserPlacement).Returns(set.Object);
var getter = DLTestHelper.New<LinqUserGetLinkedUsersForParentUser>(context.Object);
var output = getter.GetLinkedUsers(1);
var users = new List<User>();
output.ProcessDataTable((DataRow row) => students.Add(new UserStudent(row)));
Assert.AreEqual(1, users.Count);
Assert.AreEqual(2, users[0].UserId);
}
[TestMethod]
public void CanolyGetCurrentLinkedUsers()
{
var up=新列表
{
this.UserPlacementFactory(1,2,1),//创建当前的用户位置
this.UserPlacementFactory(1,3,2,false)//创建一个非当前的用户位置
}.AsQueryable();
var set=DLTestHelper.GetMockSet(up);
var context=DLTestHelper.context;
Setup(c=>c.par\u UserPlacement).Returns(set.Object);
var getter=DLTestHelper.New(context.Object);
var输出=getter.GetLinkedUsers(1);
var users=新列表();
output.ProcessDataTable((DataRow行)=>students.Add(newuserstudent(row));
Assert.AreEqual(1,users.Count);
Assert.AreEqual(2,用户[0].UserId);
}
编辑2:这是有关测试的消息和调试跟踪:
Test Result: Failed
Message: Assert.AreEqual failed. Expected:<1>. Actual:<0>
Debug Trace: This function can only be invoked from LINQ to Entities
测试结果:失败
消息:Assert.AreEqual失败。预期:。实际:
调试跟踪:此函数只能从LINQ调用到实体
从我所读到的内容来看,这是因为这个地方没有一个可以用于单元测试的该方法的LINQ to Entities实现,尽管在实时版本上有(因为它正在查询SQL server)。嗯,不确定,但您不能这样做吗
context.Setup(s => DbFunctions.TruncateTime(It.IsAny<DateTime>()))
.Returns<DateTime?>(new Func<DateTime?,DateTime?>(
(x) => {
/* whatever modification is required here */
return x; //or return modified;
}));
context.Setup(s=>DbFunctions.TruncateTime(It.IsAny()))
.Returns(新函数)(
(x) =>{
/*这里需要做什么修改*/
return x;//或return modified;
}));
查看以下答案:
老实说,考虑到这一点,我完全同意答案,并且通常遵循以下原则:我的EF查询是针对数据库进行测试的,只有我的应用程序代码是使用Moq进行测试的
使用Moq测试上面的查询的EF查询,似乎没有一个优雅的解决方案,尽管有一些黑客的想法。例如,下面的答案。两个看起来都可以为你工作
另一种测试查询的方法是在我工作过的另一个项目上实现的方法:使用VS开箱即用单元测试,每个查询(再次重构为自己的方法)测试将包装在一个事务范围中。然后,项目的测试框架将负责手动将虚假数据输入数据库,查询将尝试过滤这些虚假数据。最后,事务从未完成,因此它被回滚由于事务作用域的性质,对于许多项目来说,这可能不是一个理想的方案。很可能不是在产品环境中
否则,如果你必须继续模仿功能,你可能想考虑其他嘲笑框架。
< P>谢谢所有的帮助,我设法找到了一个解决问题的方法。在添加EntityFramework的假程序集后,我可以通过将这些测试更改为以下内容来修复它们:[TestMethod]
public void CanOnlyGetCurrentLinkedUsers()
{
using (ShimsContext.Create())
{
System.Data.Entity.Fakes.ShimDbFunctions.TruncateTimeNullableOfDateTime =
(DateTime? input) =>
{
return input.HasValue ? (DateTime?)input.Value.Date : null;
};
var up = new List<par_UserPlacement>
{
this.UserPlacementFactory(1, 2, 1), // Create a user placement that is current
this.UserPlacementFactory(1, 3, 2, false) // Create a user placement that is not current
}.AsQueryable();
var set = DLTestHelper.GetMockSet<par_UserPlacement>(up);
var context = DLTestHelper.Context;
context.Setup(c => c.par_UserPlacement).Returns(set.Object);
var getter = DLTestHelper.New<LinqUserGetLinkedUsersForParentUser>(context.Object);
var output = getter.GetLinkedUsers(1);
}
var users = new List<User>();
output.ProcessDataTable((DataRow row) => users.Add(new User(row)));
Assert.AreEqual(1, users.Count);
Assert.AreEqual(2, users[0].UserId);
}
[TestMethod]
public void CanolyGetCurrentLinkedUsers()
{
使用(ShimsContext.Create())
{
System.Data.Entity.Fakes.ShimDbFunctions.TruncateTimeNullableOfDateTime=
(日期时间?输入)=>
{
返回input.HasValue?(DateTime?)input.Value.Date:空;
};
var up=新列表
{
this.UserPlacementFactory(1,2,1),//创建当前的用户位置
this.UserPlacementFactory(1,3,2,false)//创建一个非当前的用户位置
}.AsQueryable();
var set=DLTestHelper.GetMockSet(up);
var context=DLTestHelper.context;
Setup(c=>c.par\u UserPlacement).Returns(set.Object);
var getter=DLTestHelper.New(context.Object);
var输出=getter.GetLinkedUsers(1);
}
var users=新列表();
output.ProcessDataTable((DataRow行)=>users.Add(新用户(行));
Assert.AreEqual(1,users.Count);
Assert.AreEqual(2,用户[0].UserId);
}
有一种方法可以做到这一点。由于业务逻辑的单元测试通常是鼓励的,并且由于业务逻辑对应用程序数据发出LINQ查询是完全可以的,那么对这些LINQ查询进行单元测试必须是完全可以的
不幸的是,Entity Framework的DbFunctions特性使我们无法对包含LINQ查询的代码进行单元测试。钼
var orderIdsByDate = (
from o in repo.Orders
group o by o.PlacedAt.Date
// here we used DateTime.Date
// and **NOT** DbFunctions.TruncateTime
into g
orderby g.Key
select new { Date = g.Key, OrderIds = g.Select(x => x.Id) });
public class EntityRepository<T> : IQueryable<T> where T : class
{
private readonly ObjectSet<T> _objectSet;
private InterceptingQueryProvider _queryProvider = null;
public EntityRepository<T>(ObjectSet<T> objectSet)
{
_objectSet = objectSet;
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return _objectSet.AsEnumerable().GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return _objectSet.AsEnumerable().GetEnumerator();
}
Type IQueryable.ElementType
{
get { return _objectSet.AsQueryable().ElementType; }
}
System.Linq.Expressions.Expression IQueryable.Expression
{
get { return _objectSet.AsQueryable().Expression; }
}
IQueryProvider IQueryable.Provider
{
get
{
if ( _queryProvider == null )
{
_queryProvider = new InterceptingQueryProvider(_objectSet.AsQueryable().Provider);
}
return _queryProvider;
}
}
// . . . . . you may want to include Insert(), Update(), and Delete() methods
}
private class InterceptingQueryProvider : IQueryProvider
{
private readonly IQueryProvider _actualQueryProvider;
public InterceptingQueryProvider(IQueryProvider actualQueryProvider)
{
_actualQueryProvider = actualQueryProvider;
}
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
var specializedExpression = QueryExpressionSpecializer.Specialize(expression);
return _actualQueryProvider.CreateQuery<TElement>(specializedExpression);
}
public IQueryable CreateQuery(Expression expression)
{
var specializedExpression = QueryExpressionSpecializer.Specialize(expression);
return _actualQueryProvider.CreateQuery(specializedExpression);
}
public TResult Execute<TResult>(Expression expression)
{
return _actualQueryProvider.Execute<TResult>(expression);
}
public object Execute(Expression expression)
{
return _actualQueryProvider.Execute(expression);
}
}
public static class QueryExpressionSpecializer
{
private static readonly MethodInfo _s_dbFunctions_TruncateTime_NullableOfDateTime =
GetMethodInfo<Expression<Func<DateTime?, DateTime?>>>(d => DbFunctions.TruncateTime(d));
private static readonly PropertyInfo _s_nullableOfDateTime_Value =
GetPropertyInfo<Expression<Func<DateTime?, DateTime>>>(d => d.Value);
public static Expression Specialize(Expression general)
{
var visitor = new SpecializingVisitor();
return visitor.Visit(general);
}
private static MethodInfo GetMethodInfo<TLambda>(TLambda lambda) where TLambda : LambdaExpression
{
return ((MethodCallExpression)lambda.Body).Method;
}
public static PropertyInfo GetPropertyInfo<TLambda>(TLambda lambda) where TLambda : LambdaExpression
{
return (PropertyInfo)((MemberExpression)lambda.Body).Member;
}
private class SpecializingVisitor : ExpressionVisitor
{
protected override Expression VisitMember(MemberExpression node)
{
if ( node.Expression.Type == typeof(DateTime?) && node.Member.Name == "Date" )
{
return Expression.Call(_s_dbFunctions_TruncateTime_NullableOfDateTime, node.Expression);
}
if ( node.Expression.Type == typeof(DateTime) && node.Member.Name == "Date" )
{
return Expression.Property(
Expression.Call(
_s_dbFunctions_TruncateTime_NullableOfDateTime,
Expression.Convert(
node.Expression,
typeof(DateTime?)
)
),
_s_nullableOfDateTime_Value
);
}
return base.VisitMember(node);
}
}
}
public System.Data.DataTable GetLinkedUsers(int parentUserId, bool useDbFunctions = true)
{
var today = DateTime.Now.Date;
var queryable = from up in DB.par_UserPlacement
where up.MentorId == mentorUserId;
if (useDbFunctions) // use the DbFunctions
{
queryable = queryable.Where(up =>
DbFunctions.TruncateTime(today) >= DbFunctions.TruncateTime(up.StartDate)
&& DbFunctions.TruncateTime(today) <= DbFunctions.TruncateTime(up.EndDate));
}
else
{
// do db-functions equivalent here using C# logic
// this is what the unit test path will invoke
queryable = queryable.Where(up => up.StartDate < today);
}
var query = from up in queryable
select new
{
up.UserPlacementId,
up.Users.UserId,
up.Users.FirstName,
up.Users.LastName,
up.Placements.PlacementId,
up.Placements.PlacementName,
up.StartDate,
up.EndDate,
};
query = query.OrderBy(up => up.EndDate);
return this.RunQueryToDataTable(query);
}
GetLinkedUsers(parentUserId: 10, useDbFunctions: false);
public static class CanTestDbFunctions
{
[System.Data.Entity.DbFunction("Edm", "TruncateTime")]
public static DateTime? TruncateTime(DateTime? dateValue)
{
...
}
}
[DbFunction("Edm", "TruncateTime")]
public static DateTime? TruncateTime(DateTime? dateValue)
{
return dateValue?.Date;
}