当前位置: 首页 > 编程语言 > C#

如何在单元测试中使用Moq和DbFunction来防止NotSupportedException?分享

时间:2023-04-10 15:26:58 C#

单元测试中如何使用Moq和DbFunction防止NotSupportedException?我目前正在尝试对通过实体框架运行的查询运行一些单元测试。查询本身在实时版本上运行没有任何问题,但单元测试总是失败。我将其缩小到我对DbFunctions.TruncateTime的使用,但我不知道如何以这种方式进行单元测试以反映实时服务器上发生的事情。这是我正在使用的方法:publicSystem.Data.DataTableGetLinkedUsers(intparentUserId){vartoday=DateTime.Now.Date;varquery=fromupinDB.par_UserPlacementwhereup.MentorId==mentorUserId&&DbFunctions.TruncateTime(today)>=DbFunctions.TruncateTime(up.StartDate)&&DbFunctions.TruncateTime(today)up.EndDate);返回this.RunQueryToDataTable(query);如果我用DbFunctions注释掉该行,则测试全部通过(除了检查仅运行那些在给定日期具有有效结果的测试)。有没有办法提供这些测试中使用的DbFunctions.TruncateTime的模拟版本?本质上它应该只返回Datetime.Date,但它在EF查询中不可用。编辑:这是日期检查失败的测试:[TestMethod]publicvoidCanOnlyGetCurrentLinkedUsers(){varup=newList{this.UserPlacementFactory(1,2,1),//创建当前的用户位置this.UserPlacementFactory(1,3,2,false)//创建一个非当前的用户展示位置}.AsQueryable();varset=DLTestHelper.GetMockSet(up);varcontext=DLTestHelper.Context;context.Setup(c=>c.par_UserPlacement).Returns(set.Object);vargetter=DLTestHelper.New(context.Object);varoutput=getter.GetLinkedUsers(1);变种用户=新列表();output.ProcessDataTable((DataRowrow)=>students.Add(newUserStudent(row)));Assert.AreEqual(1,users.Count);Assert.AreEqual(2,users[0].UserId);编辑2:这是来自相关测试跟踪的消息和调试:测试结果:失败消息:Assert.AreEqual失败。预期的:。Actual:DebugTrace:ThisfunctioncanonlybeinvokedfromLINQtoEntities从我读到的,这是因为没有LINQtoEntities实现这个方法可以被调用这个地方用于单元测试,虽然有一个live版本(因为它正在查询SQL服务器)。我知道我迟到了,但一个非常简单的解决方法是编写您自己的使用DbFunction属性的方法。然后使用该函数代替DbFunctions.TruncateTime。[DbFunction("Edm","TruncateTime")]公共静态日期时间?TruncateTime(DateTime?dateValue){返回dateValue?.Date;使用此函数将在使用LinqtoEntities时执行EDMTruncateTime方法,否则将运行提供的代码。感谢大家的帮助,在阅读了qujck提到的shim后,我设法找到了适合我的解决方案。添加一个假的EntityFramework程序集后,我能够通过将它们更改为以下内容来修复这些测试:=(DateTime?输入)=>{返回输入。HasValue?(DateTime?)input.Value.Date:null;};varup=newList{this.UserPlacementFactory(1,2,1),//创建一个当前用户位置this.UserPlacementFactory(1,3,2,false)//创建一个非当前用户位置}.AsQueryable();varset=DLTestHelper.GetMockSet(up);varcontext=DLTestHelper.Context;context.Setup(c=>c.par_UserPlacement).Returns(set.Object);vargetter=DLTestHelper.New(context.Object);varoutput=getter.GetLinkedUsers(1);}varusers=newList();output.ProcessDataTable((DataRowrow)=>users.Add(newUser(row)));Assert.AreEqual(1,users.Count);Assert.AreEqual(2,users[0].UserId);看到这个回答:https://stackoverflow.com/a/14975425/1509728老老实实想想之后我完全同意答案并且通常遵循我的EF查询针对数据库进行测试的原则并且只使用Moq来测试我的应用程序代码看起来没有优雅的解决方案使用Moq来测试上述查询的EF查询相反,有一些hacky的想法。例如这个和随后的答案。两者似乎都适合你。另一种测试查询的方法是在我从事的另一个项目中实现的方法:使用开箱即用的VS单元测试,每个查询(再次重构为它自己的方法)测试将包装在事务范围内。然后项目的测试框架将负责手动将假数据输入数据库,查询将尝试过滤这些假数据。最后,事务永远不会完成,所以它被回滚了。由于事务范围的性质,这对于许多项目来说可能不是理想的场景。很可能不在生产环境中。否则,如果您必须继续模拟函数,您可能需要考虑其他模拟框架。有很多方法可以做到这一点。由于通常鼓励对业务逻辑进行单元测试,并且由于业务逻辑可以针对应用程序数据发出LINQ查询,因此对这些LINQ查询进行单元测试也一定是完全可以的。不幸的是,实体框架的DbFunctions功能阻止我们对包含LINQ查询的代码进行单元测试。此外,在业务逻辑中使用DbFunction在架构上是错误的,因为它将业务逻辑层与特定的持久性技术结合在一起(这是一个单独的讨论)。话虽如此,我们的目标是能够像这样运行LINQ查询:*DbFunctions.TruncateTimeintogorderbyg.Keyselectnew{Date=g.Key,OrderIds=g.Select(x=>x.Id)});在单元测试中,这将归结为针对预先安排的普通实体数组运行LINQtoobjects(例如)。在实际操作中,必须针对实体框架的真实ObjectContext。这是一种方法-不过,您需要执行几个步骤。我正在截取一个真实的例子:第1步。将ObjectSet包装在我们自己的IQueryable实现中,以便我们提供我们自己的IQueryProvider拦截包装器。publicclassEntityRepository:IQueryablewhereT:class{privatereadonlyObjectSet_objectSet;私有拦截查询提供者_queryProvider=null;publicEntityRepository(ObjectSetobjectSet){_objectSet=objectSet;}IEnumeratorIEnumerable.GetEnumerator(){返回_objectSet.AsEnumerable().GetEnumerator();}System.Collections.IEnumeratorSystem.Collections.IEnumerable.GetEnumerator(){返回_objectSet.AsEnumerable().GetEnumerator();}输入IQueryable.ElementType{get{return_objectSet.AsQueryable().ElementType;}}System.Linq.Expressions.ExpressionIQueryable.Expression{get{return_objectSet.AsQueryable().Expression;}}IQueryProviderIQueryable.Provider{get{if(_queryProvider==null){_queryProvider=newInterceptingQueryProvider(_objectSet.AsQueryable().Provider);}返回_queryProvider;}}//。....您可能希望包括Insert()、Update()和Delete()方法}第2步。实际查询提供程序,在我的示例中,它是EntityRepository的套件类:privateclassInterceptingQueryProvider:IQueryProvider{privatereadonlyIQueryProvider_actualQueryProvider;publicInterceptingQueryProvider(IQueryProvideractualQueryProvider){_actualQueryProvider=actualQueryProvider;}publicIQueryableCreateQuery(Expressionexpression){varspecializedExpression=QueryExpressionSpecializer.Specialize(expression);返回_actualQueryProvider.CreateQuery(specializedExpression);}publicIQueryableCreateQuery(Expressionexpression){varspecializedExpression=QueryExpressionSpecializer.Specialize(expression);返回_actualQueryProvider.CreateQuery(specializedExpression);}publicTResultExecute(Expressionexpression){return_actualQueryProvider.Execute(expression);}publicobjectExecute(Expressionexpression){return_actualQueryProvider.Execute(expression);第3步。最后,实现一个名为QueryExpressionSpecializer的辅助类,它将使用DbFunctions.TruncateTime替换DateTime.Da这publicstaticclassQueryExpressionSpecializer{privatestaticreadonlyMethodInfo_s_dbFunctions_TruncateTime_NullableOfDateTime=GetMethodInfo>>(d=>DbFunctions.TruncateTime(d));privatestaticreadonlyPropertyInfo_s_nullableOfDateTime_Value=GetPropertyInfo>>(d=>d.Value);publicstaticExpressionSpecialize(Expressiongeneral){varvisitor=newSpecializingVisitor();回访者。访问(一般);}privatestaticMethodInfoGetMethodInfo(TLambdalambda)whereTLambda:LambdaExpression{return((MethodCallExpression)lambda.Body).Method;}publicstaticPropertyInfoGetPropertyInfo(TLambdalambda)whereTLambda:LambdaExpression{return(PropertyInfo)((MemberExpression)lambda.Body).Member;}privateclassSpecializingVisitor:ExpressionVisitor{protectedoverrideExpressionVisitMember(MemberExpressionnode){if(node.Expression.Type==typeof(DateTime?)&&node.Member.Name=="Date"){returnExpression.Call(_s_dbFuncions_TruncateTime_NullableOfDateTime,node.Expression);}if(node.Expression.Type==typeof(DateTime)&&node.Member.Name=="Date"){returnExpression.Property(Expression.Call(_s_dbFunctions_TruncateTime_NullableOfDateTime,Expression.Convert(node.Expression,typeof(DateTime?))),_s_nullableOfDateTime_Value);}returnbase.VisitMember(node);当然,上面的QueryExpressionSpecializer实现可以泛化,允许插入任意数量的额外转换,允许自定义类型的成员在LINQ查询中使用,即使它们不为EntityFramework所知,嗯,不确定,但是你不能做这样的事情吗?context.Setup(s=>DbFunctions.TruncateTime(It.IsAny())).Returns(newFunc((x)=>{/*这里需要任何修改*/returnx;//orreturnmodified;}));因为我最近遇到了同样的问题并选择了一个更简单的解决方案,所以想在这里发布它..这个解决方案不需要垫片,模拟,没有任何扩展等。将'useDbFunctions'布尔标志传递给你的方法,默认为真。当您的实时代码执行时,您的查询将使用DbFunctions,一切都会正常进行。由于默认,调用者无需担心。当您的单元测试调用方法进行测试时,它们可以通过useDbFunctions:false。在您的方法中,您可以使用此标志来组成您的IQueryable。如果useDbFunctions为真,则使用DbFunctions将谓词添加到可查询。如果useDbFunctions为false,则跳过DbFunctions方法调用,并执行显式C#等效解决方案。这样,您的单元测试将检查几乎95%的方式与实时代码相同。与等效代码相比,您仍然有“DbFunctions”的增量,但要勤奋,95%看起来是一个很大的收获。publicSystem.Data.DataTableGetLinkedUsers(intparentUserId,booluseDbFunctions=true){vartoday=DateTime.Now.Date;varqueryable=fromupinDB.par_UserPlacementwhereup.MentorId==mentorUserId;if(useDbFunctions)//使用DbFunctions{queryable=queryable.Where(up=>DbFunctions.TruncateTime(today)>=DbFunctions.TruncateTime(up.StartDate)&&DbFunctions.TruncateTime(today)up.StartDateup.EndDate);返回this.RunQueryToDataTable(query);单元测试将调用方法:GetLinkedUsers(parentUserId:10,useDbFunctions:false);由于单元测试将设置本地DbContext实体,因此C#逻辑/DateTime函数将起作用。Mocks的使用前段时间结束了。不要模仿,只需连接到真实数据库即可。在测试开始时重新生成/播种数据库。如果您仍想继续使用模拟,请创建您自己的方法,如下所示。IT改变行为运行时。使用真实数据库时,使用数据库函数,否则使用此方法。用此方法替换代码中的DBfunctions方法TruncateTime(DateTime?dateValue){...}}这是被调用的实际函数。请记住,时间不能从DateTime对象中删除,不能与午夜一起使用或创建等效的字符串。以上就是C#学习教程:如何在单元测试中使用Moq和DbFunction来防止NotSupportedException?如果所有分享的内容对你有用,需要进一步了解C#学习教程,希望大家多多关注。本文收集自网络,不代表立场。如涉及侵权,请点击右侧联系管理员删除。如需转载请注明出处: