如何模拟LINQ to Entities助手,例如’SqlFunctions.StringConvert()’

我正在使用EF 4,并尝试使用Moq对以下行进行unit testing:

var convertError = models .Where(x => SqlFunctions.StringConvert((decimal?) (x.convert ?? 0)) == "0") .Any(); 

并且看起来SqlFunctions.StringConvert()会在检测到上下文被SqlFunctions.StringConvert()时抛出。

它给出了一个错误说:

只能从LINQ到实体调用此函数

是否有可能告诉SqlFunctions.StringConvert返回一个模拟对象,以便我可以摆脱这个错误?

不可能因为函数的实现如下:

 [EdmFunction("SqlServer", "STR")] public static string StringConvert(decimal? number, int? length) { throw EntityUtil.NotSupported(Strings.ELinq_EdmFunctionDirectCall); } 

你不能使用Moq伪造这个function。 你需要更强大的模拟框架,它允许你替换静态函数调用 – 可能是Microsoft Fakes,TypeMock Isolator或JustMock。

或者你需要考虑你的测试方法,因为模仿上下文是错误的想法。 你应该改为:

 var convertError = myQueryProvider.ConvertQuery(x.convert); 

其中queryProvider将是隐藏查询的可模拟类型。 查询是与数据库相关的逻辑,应该针对真实数据库进行测试。 您的查询周围的代码是您的应用程序逻辑,它应该是unit testing – 正确测试它们的最佳解决方案只是通过某个接口(在这种情况下查询提供程序,但人们经常使用完整的特定存储库)将它们分开。 这个原则来自于关注点的分离 – 查询执行是一个单独的问题,因此它被放置在自己的方法中,该方法是单独测试的。

我所做的是提供我自己的DbFunction实现,使得unit testing中的LINQ To Objects使用简单的.NET实现,而LINQ To EF在运行时使用DbFunctionAttribute,方式与System.Data.Entity.DbFunctions相同。 我曾想过嘲笑DbFunctions但是,LINQ to Objects实现很有用并且工作正常。 这是一个例子:

 public static class DbFunctions { [DbFunction("Edm", "AddMinutes")] public static TimeSpan? AddMinutes(TimeSpan? timeValue, int? addValue) { return timeValue == null ? (TimeSpan?)null : timeValue.Value.Add(new TimeSpan(0, addValue.Value, 0)); } } 

你可以模拟EdmFunctions,我使用NSubstitute(它也不支持模拟静态函数)完成了这个。 诀窍是将DbContext包装在一个接口中。 然后,将静态EdmFunction函数添加到静态类,并在静态类中为您的上下文创建扩展方法以调用该方法。 例如

 public static class EdmxExtensions { [EdmFunction("SqlServer", "STR")] public static string StringConvert(decimal? number, int? length) { throw EntityUtil.NotSupported(Strings.ELinq_EdmFunctionDirectCall); } public static IQueryable MyFunction(this IDbContext context, decimal? number, int? length) { context.Person.Where(s => StringConvert(s.personId, number, length); } 

然后,您将能够模拟MyFunction,因为它是一个可用于接口的方法,并且当您尝试调用它时,EntityFramework不会生气。

我没有尝试过Moq,但你可以用类似的方式做到这一点。

另一种方法是您可以编写自己的方法,该方法具有相同的属性标记和方法签名,然后实际实现方法unit testing目的而不是抛出exception。 entity framework忽略了函数中的代码,因此它永远不会调用它。