【问题标题】:Unit Test SqlFunctions单元测试 SqlFunctions
【发布时间】:2018-05-19 20:47:45
【问题描述】:

我有一个公开 IQueryable 的存储库,一个服务处理特定的查询,这里有一些使用 DbFunctions 的方法。为了可测试,我创建了一个带有静态元素列表的假存储库并将其注入到服务中。问题是,由于我的服务查询一个列表并且不使用数据库,我收到错误“此函数只能从 LINQ 调用到实体。”。

有没有比创建一个假的 DbFunctions 和 QueryProvider 更简单的测试方法?

提前致谢

【问题讨论】:

  • 那只是one of many required replacements。做集成测试。模拟永远不会可靠或需要太多代码(+维护),以至于它们本身就成为一个应用程序。您需要为您的单元测试框架进行单元测试!
  • 谢谢格特!确实很难模拟 DbFunctions,我已经能够制作一些我需要的东西,但就像你说的那样,虽然是维护。

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


【解决方案1】:

我尝试实现 dateDiff 函数,它对我有用 但我们应该认为在这种情况下我们测试不同的功能 我们正在测试的不是真实行为

 private class MySqlFunctions
    {
        [DbFunction("SqlServer", "DATEDIFF")]//EF will use this function
        public int? DateDiff(string datePartArg, DateTime startDate, DateTime endDate)
        {
            var subtract = startDate.Subtract(endDate);
            switch (datePartArg)
            {
                case "d":
                    return (int?)subtract.TotalDays;
                case "s":
                    return (int?)subtract.TotalSeconds; // unit test will use this one
            }
            throw new NotSupportedException("Method supports only s or d param");
        }
    }

然后在linq代码中

var sqlFunctions = new MySqlFunctions();

var result = matches.Average(s => sqlFunctions.DateDiff("s", s.MatchCreated, s.WaitingStarted);

【讨论】:

    【解决方案2】:

    您无法可靠地伪造 SQL,因为 LINQ to 对象在许多情况下的行为与 LINQ to SQL 不同。例如,(new [] { "asdf"}).Contains("ASDF") 在 LINQ to 对象中返回 false,则 LINQ to SQL 中相同类型的查询将返回 true。我发现做的最好的事情是将数据的检索与对该数据的操作分开。也许创建某种将 IPersonRepository 作为依赖项的 PersonManager。您可以伪造/模拟 IPersonRepository 并使用它来测试 PersonManager 在各种情况下执行它应该执行的操作。

    【讨论】:

    • 嗨,亚历克斯,感谢您的回复。我刚刚在这里找到了同样问题的人:stackoverflow.com/questions/14883360/… 如果您在 EntityFunctionsFake 类上创建具有相同签名的 Contains 方法并在比较时指定 IgnoreCase,则可以使用给定的解决方案解决您给出的示例。它可能并不适合所有情况,但至少我现在可以测试我的要求。你怎么看?干杯
    • 你不能在 LINQ 查询中使用 IgnoreCase。
    【解决方案3】:

    因为我最近遇到了同样的问题,并选择了一个更简单的解决方案,所以想在这里发布..这个解决方案不需要垫片、模拟,没有任何扩展等。

    1. 将“useDbFunctions”布尔标志传递给您的方法,默认值为 true。
    2. 当您的实时代码执行时,您的查询将使用 DbFunctions,一切都会正常进行。由于是默认值,调用者无需担心。
    3. 当您的单元测试调用该方法进行测试时,它们可以传递 useDbFunctions: false。
    4. 在您的方法中,您可以使用标志来组成您的 IQueryable.. 如果 useDbFunctions 为真,则使用 DbFunctions 将谓词添加到可查询对象。 如果 useDbFunctions 为 false,则跳过 DbFunctions 方法调用,并执行显式 C# 等效解决方案。

    这样,您的单元测试将检查几乎 95% 的方法与实时代码相同。与等效代码相比,您仍然有“DbFunctions”的增量,但要勤奋,95% 看起来会有很多收获。

    public SomeMethodWithDbFunctions(bool useDbFunctions = true)
    {
        var queryable = db.Employees.Where(e=>e.Id==1); // without the DbFunctions
    
        if (useDbFunctions) // use the DbFunctions
        {
         queryable = queryable.Where(e=> 
         DbFunctions.AddSeconds(e.LoginTime, 3600) <= DateTime.Now);
        }  
        else
        {
          // do db-functions equivalent here using C# logic
          // this is what the unit test path will invoke
          queryable = queryable.Where(e=>e.LoginTime.AddSeconds(3600) < DateTime.Now);
        }                    
    
        var query = queryable.Select(); // do projections, sorting etc.
    }
    

    单元测试将调用该方法:

    SomeMethodWithDbFunctions(useDbFunctions: false);
    

    因为单元测试会设置本地 DbContext 实体,所以 C# 逻辑/日期时间函数可以工作。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-01-08
      • 1970-01-01
      • 1970-01-01
      • 2011-07-07
      • 2011-04-12
      • 2023-03-21
      • 2011-05-04
      相关资源
      最近更新 更多