单元测试SqlFunctions

时间:2014-01-16 13:51:23

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

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

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

提前致谢

3 个答案:

答案 0 :(得分: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);

答案 1 :(得分:0)

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

答案 2 :(得分:0)

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

  1. 将'useDbFunctions'布尔标志传递给您的方法,默认值为true。
  2. 当您的实时代码执行时,您的查询将使用DbFunctions,一切都会正常工作。由于默认值,呼叫者无需担心它。
  3. 当您的单元测试调用方法进行测试时,它们可以传递useDbFunctions:false。
  4. 在你的方法中,你可以使用旗帜来组成你的IQueryable .. 如果useDbFunctions为true,则使用DbFunctions将谓词添加到queryable。 如果useDbFunctions为false,则跳过DbFunctions方法调用,并执行显式的C#等效解决方案。
  5. 这样,您的单元测试将检查几乎95%的方法与实时代码相同。你仍然有“DbFunctions”的delta与你的等效代码相比,但要勤奋,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#logic / DateTime函数可以工作。