如何模拟LINQ to Entities帮助程序,如';SqlFunctions.StringConvert()&
本文关键字:StringConvert SqlFunctions to 何模拟 模拟 LINQ Entities 帮助程序 | 更新日期: 2023-09-27 18:00:06
我正在使用EF 4,并试图使用Moq:对以下行进行单元测试
var convertError = models
.Where(x => SqlFunctions.StringConvert((decimal?) (x.convert ?? 0)) == "0")
.Any();
并且如果CCD_ 1检测到上下文被嘲弄,它似乎将抛出。
它给出了一个错误:
此函数只能从LINQ到实体调用
有没有可能告诉SqlFunctions.StringConvert
返回一个mock对象,这样我就可以摆脱这个错误?
我所做的是提供我自己的DbFunctions实现,这样单元测试中的LINQ to Objects使用一个简单的.NET实现,而LINQ to EF在运行时使用DbFunctionAttribute的方式与System.Data.Entity.DbFunctions相同。我曾想过嘲笑DbFunction,但嘿,LINQ到对象的实现很有用,而且工作得很好。这里有一个例子:
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));
}
}
不可能,因为函数的实现看起来像:
[EdmFunction("SqlServer", "STR")]
public static string StringConvert(decimal? number, int? length)
{
throw EntityUtil.NotSupported(Strings.ELinq_EdmFunctionDirectCall);
}
您不能使用Moq来伪造此函数。您需要更强大的mocking框架,它将允许您替换静态函数调用——可能是Microsoft Fakes、TypeMock Isolator或JustLock。
或者你需要考虑你的测试方法,因为嘲笑上下文是错误的想法。你应该有这样的东西:
var convertError = myQueryProvider.ConvertQuery(x.convert);
其中queryProvider
将是隐藏查询的可模拟类型。查询是与数据库相关的逻辑,应该针对真实的数据库进行测试。围绕查询的代码是您的应用程序逻辑,应该对其进行单元测试-正确测试两者的最佳解决方案只是通过一些接口将它们分离(在这种情况下是查询提供者,但人们通常使用完整的特定存储库)。这一原则来自关注点的分离——查询执行是独立的关注点,因此它被放入自己的方法中,该方法被单独测试。
您可以模拟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<Person> MyFunction(this IDbContext context, decimal? number, int? length)
{
context.Person.Where(s => StringConvert(s.personId, number, length);
}
然后,您将能够模拟MyFunction,因为它是一个可用于接口的方法,当您尝试调用它时,EntityFramework不会生气。
我还没有在Moq身上尝试过,但你可以用类似的方式来做。
另一种方法是,您可以编写自己的方法,该方法具有相同的属性标记和方法签名,然后实际实现方法单元测试目的,而不是抛出异常。实体框架忽略函数中的代码,因此它永远不会调用它。
不能告诉SqlFunctions.StringConvert
返回mock对象,因为它是一个静态方法。但是您可以为它创建一个接口并创建一个facade类。
创建一个这样的界面,并确保包含属性
public interface ISqlFunctions
{
[System.Data.Entity.Core.Objects.DataClasses.EdmFunction("SqlServer", "STR")]
string StringConvert(Decimal? number);
}
然后写你的门面课。这应该是C#做任何你想让Linq到实体做的事情的方式。
public class SqlFunctionsFacade : ISqlFunctions
{
public string StringConvert(decimal? number)
{
return number?.ToString();
}
}
在您的实现中,在您的linq查询中使用您的接口
public SomethingOrOther(ISqlFunctions sqlFunctions)
{
var convertError = models
.Where(x => sqlFunctions.StringConvert((decimal?)(x.convert ?? 0)) == "0")
.Any();
}
实体框架将以与SqlFunctions.StringConvert(decimal?)
相同的方式在接口上使用该属性。
在单元测试中,您可以为被测系统提供facade类或接口的mock。
使用System.Data.Entity.DbFunctionAttribute并创建您的"短截尾的";EF DbFunction的实现。然后当你运行你的应用程序时,它将使用EF实现,当你运行单元测试时;短截尾的";实现开始发挥作用。
"Stubbed";最接近MSSQL的Str()使用新的System.Data.Entity.DbFunctionAttribute所做的实现。对于那些不喜欢在重新发明轮子上浪费时间,但为了单元测试而需要它的人来说。享受
[DbFunction("SqlServer", "STR")]
public static string StringConvert(double? number, int? length, int? decimalArg)
{
if (number == null)
return null;
var roundedValue = decimalArg != null
? Math.Round(number.Value, decimalArg.Value).ToString($"##.{new string('0', decimalArg.Value)}")
: number.Value.ToString(CultureInfo.InvariantCulture);
return length != null && length - roundedValue.Length > 0
? $"{roundedValue}{new string(' ', length.Value - roundedValue.Length)}"
: roundedValue;
}