6

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

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

SqlFunctions.StringConvert()如果它检测到上下文被嘲笑,它似乎会抛出。

它给出了一个错误说:

此函数只能从 LINQ to Entities 调用

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

4

6 回答 6

7

我所做的是提供我自己的 DbFunctions 实现,以便单元测试中的 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));
    }
}
于 2014-12-04T02:02:53.200 回答
6

不,这是不可能的,因为函数的实现看起来像:

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

于 2013-02-20T08:37:42.790 回答
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 在您尝试调用它时不会生气。

我还没有在起订量上试过这个,但你可以用类似的方式做到这一点。

于 2013-03-07T20:53:00.897 回答
2

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

于 2013-03-08T18:20:05.713 回答
0

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

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

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

然后编写你的外观类。这应该是您希望 Linq to 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?).

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

于 2019-05-24T19:29:10.300 回答
0

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

“存根”实现最接近MSSQL 中的Str()使用 new 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;
    }
于 2020-09-08T14:34:28.407 回答