【问题标题】:How do I mock multiple levels of DbSet.Include lambdas?如何模拟多个级别的 DbSet.Include lambdas?
【发布时间】:2015-06-15 16:23:10
【问题描述】:

我正在使用 Moq 编写使用 Entity Framework 6 DbSetDbContext 对象的单元测试。我有一个带有级联/多级Include 的服务方法,但我不知道如何设置它以进行测试。服务方法如下所示:

return DataContext.Cars
    .Include(p => p.Model)
    .Include(p => p.Model.Make)
    .Select(c => new 
         {
             Key = c.CarId, 
             Value = string.Format("{0} {1} {2}", c.Model.Make.Name, c.Model.Name, c.Trim)
         }
    ).ToArray();

我知道我必须设置 Include 来返回模拟对象,如下所示:

mockCarDbSet.Setup(m => m.Include(It.IsAny<string>())).Returns(mockCarSet.Object);

但是我从级联的.Include(p =&gt; p.Model.Make) 中得到一个空引用异常。如何设置 Moq 以处理多个级别的Include

编辑
好的,事实证明我不能将It.IsAny&lt;string&gt; 用于使用lambdas 而不是字符串的Include 调用,所以现在我有两个问题:

  1. 如何使用 Include 设置一个接受 lambda 的模拟?
  2. 上述设置是否会级联到多个级别?

【问题讨论】:

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


    【解决方案1】:

    include() 是一个静态方法(扩展方法)。 Moq 不支持静态方法 mock(read this link)。

    要测试您的代码,您需要将mockCarDbSet 设置为返回IQueryable&lt;Car&gt;

    var carQuery = new List<Car>
    {
        //add cars
    }
    IQueryable<Post> query = carQuery.AsQueryable();
    

    作为DataContext.Cars的结果返回query

    这些步骤将解决静态方法问题。

    【讨论】:

    • 实际上,Moq 可以很好地处理 .Include(It.IsAny),这对 DbSet.Include("SomeEntity") 非常有效。看到这个答案:stackoverflow.com/a/21979183/27457
    • 链接中的例子使用了对象方法。此方法获取一个字符串作为参数(the link method)。在您的代码中,您使用扩展方法。
    • 这里的小问题是第一次使用 if Include(...) 是 DbSet(可以模拟)的方法,然后返回 IQueryable。因此,第二个调用是使用 IQueryable. 的静态 Include(...) 扩展方法执行的
    • @Phil , 嗯...我认为您应该再次查看 OP 代码...当我写信给 OP 时,解决方法是将 DataContext.Cars 设置为返回列表汽车......顺便说一句,3 个月后我发布了this answer to the question: How to write this EF Mock setup code as a reusable Generic Boilerplate?(与这个问题相关......)
    • @老狐狸。感谢您的建议。只是我发现该答案仅在使用 DbSet 的 Include 方法时有效,但不适用于 QueryableExtensions.Include 方法。
    【解决方案2】:

    感谢@Old Fox 提醒我 Moq 不适用于静态成员,我找到了一种使用 Microsoft Fakes 的方法。 Shims 允许您填充静态方法。我使用 Moq 为每个实体设置 Mock&lt;DbSet&gt; 对象:

    var carData = new List<Car>{new Car{ Trim = "Whatever" }};  
    var mockCarSet = new Mock<DbSet<Car>>();
    mockCarSet.As<IQueryable<Car>>().Setup(m => m.Provider).Returns(carData.Provider);
    mockCarSet.As<IQueryable<Car>>().Setup(m => m.Expression).Returns(carData.Expression);
    mockCarSet.As<IQueryable<Car>>().Setup(m => m.ElementType).Returns(carData.ElementType);
    mockCarSet.As<IQueryable<Car>>().Setup(m => m.GetEnumerator()).Returns(carData.GetEnumerator);
    var mockMakeSet = new Mock<DbSet<Make>>();
    //do the same stuff as with Car for IQueryable Setup
    var mockModelSet = new Mock<DbSet<Model>>();
    //do the same stuff as with Car for IQueryable Setup
    using(ShimsContext.Create())
    {
        //hack to return the first, since this is all mock data anyway
        ShimModel.AllInstances.MakeGet = model => mockMakeSet.Object.First();
        ShimCar.AllInstances.ModelGet = car => mockModelSet.Object.First();
        //run the test
    }
    

    【讨论】:

      猜你喜欢
      • 2012-08-29
      • 1970-01-01
      • 2011-06-17
      • 1970-01-01
      • 1970-01-01
      • 2014-06-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多