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

时间:2013-02-19 22:11:34

标签: c# unit-testing entity-framework-4 mocking moq

我正在使用EF 4并尝试使用Moq单元测试以下行:

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

如果检测到上下文被模拟,似乎SqlFunctions.StringConvert()将抛出。

它出错了:

  

此功能只能从LINQ to Entities

调用

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

6 个答案:

答案 0 :(得分:5)

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

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

您无法使用Moq伪造此功能。您需要更强大的模拟框架,它允许您替换静态函数调用 - 可能是Microsoft Fakes,TypeMock Isolator或JustMock。

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

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

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

答案 1 :(得分:5)

我所做的是提供我自己的DbFunction实现,以便单元测试中的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));
    }
}

答案 2 :(得分:2)

您可以模拟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,但你可以用类似的方式做到这一点。

答案 3 :(得分:1)

另一种方法是您可以编写自己的方法,该方法具有相同的属性标记和方法签名,然后实际实现方法单元测试目的而不是抛出异常。实体框架忽略了函数中的代码,因此它永远不会调用它。

答案 4 :(得分:0)

您不能告诉SqlFunctions.StringConvert返回模拟对象,因为它是静态方法。但是您可以为其创建一个接口并创建一个Facade类。

像这样创建一个界面,并确保包含属性

public interface ISqlFunctions
{
    [System.Data.Entity.Core.Objects.DataClasses.EdmFunction("SqlServer", "STR")]
    string StringConvert(Decimal? number);
}

然后编写您的Facade类。这应该是您希望Linq进行Entity的任何一种C#方法。

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?)相同的方式在接口上使用属性。

在单元测试中,可以为您的被测系统提供外观类或接口模拟。

答案 5 :(得分:0)

使用 System.Data.Entity.DbFunctionAttribute 并创建EF DbFunction的“存根”实现。然后,当您运行您的应用程序时,它将使用EF实现,而当您运行单元测试时,您的“存根”实现将发挥作用。

最接近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;
    }