Moq测试LINQ哪里有查询

Mar*_*ith 15 linq tdd unit-testing moq entity-framework-4.1

我正在使用EF 4.1来构建域模型.我有一个带有Validate(字符串userCode)方法的Task类,在其中我想确保用户代码映射到数据库中的有效用户,因此:

public static bool Validate(string userCode)
{
    IDbSet<User> users = db.Set<User>();
    var results = from u in users
              where u.UserCode.Equals(userCode)
              select u;
    return results.FirstOrDefault() != null;
}
Run Code Online (Sandbox Code Playgroud)

我可以使用Moq来模拟IDbSet没问题.但是在Where调用时遇到了麻烦:

User user = new User { UserCode = "abc" };
IList<User> list = new List<User> { user };
var users = new Mock<IDbSet<User>>();
users.Setup(x => x.Where(It.IsAny<Expression<Func<User, bool>>>())).Returns(list.AsQueryable);

Initialization method JLTi.iRIS3.Tests.TaskTest.SetUp threw exception.
System.NotSupportedException: System.NotSupportedException: Expression 
references a method that does not belong to the mocked object:
x => x.Where<User>(It.IsAny<Expression`1>()).
Run Code Online (Sandbox Code Playgroud)

除了创建一个间接级别(例如,使用ServiceLocator来获取运行LINQ的对象然后模拟该方法)之外,我想不出怎么测试这个,但我想确保以前没办法我介绍另一层.而且我可以看到经常需要这种LINQ查询,因此服务对象很快就会失控.

某种灵魂有帮助吗?谢谢!

Ale*_*ith 20

有一篇关于MSDN的文章突出显示了如何使用moq进行模拟:它的要点是将linq表示为具有linq到对象的实体操作.

var mockSet = new Mock<DbSet<Blog>>(); 
mockSet.As<IQueryable<Blog>>().Setup(m => m.Provider).Returns(data.Provider); 
mockSet.As<IQueryable<Blog>>().Setup(m => m.Expression).Returns(data.Expression); 
mockSet.As<IQueryable<Blog>>().Setup(m => m.ElementType).Returns(data.ElementType); 
mockSet.As<IQueryable<Blog>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());
Run Code Online (Sandbox Code Playgroud)

正如Ladislav所指出的那样,由于Linq To Objects与Linq to Entities的不同,因此可能会导致误报.但它现在是一篇MSDN文章,它确实指出它至少是可能的,也许在某些情况下推荐?

自从这篇文章的原始答案以来可能发生的一件事是,实体框架团队已经在EF 6.0中开辟了实体框架领域,以便更容易模仿它的内部.

  • 凉爽的!我今天需要这个,太巧了,你也刚刚发布了它:) 它对我来说很好用。 (2认同)

aqw*_*ert 10

虽然我没有尝试过这个,但是因为IDBSet实现IEnumerable你可能需要模拟枚举器方法,所以linq语句会选择你的用户列表.您实际上并不想模拟linq,但是根据代码的外观,您要测试是否根据UserCode我认为是有效的单元测试来找到合适的用户.

 var user = new User { UserCode = "abc" };
 var list = new List<User> { user };
 var users = new Mock<IDbSet<User>>();
 users.Setup(x => x.GetEnumerator()).Returns(list.GetEnumerator());
Run Code Online (Sandbox Code Playgroud)

您可能会与非通用版本发生冲突,GetEnumerator但这可能会帮助您走上正确的道路.然后,您必须将模拟对象放在数据上下文中,这取决于我们看不到的其他代码.

  • 我做到了,但随后在该模拟对象上执行`.Where`会抛出异常. (9认同)

Lad*_*nka 9

据我所知,Moq只能设置模拟对象本身的虚方法,但是你试图设置扩展(静态)方法 - 没办法!这些方法绝对不在您的模拟范围之内.

此外,该代码很难测试,需要太多的初始化才能测试它.请改用:

internal virtual IQueryable<User> GetUserSet()
{
    return db.Set<User>();
} 

public bool Validate(string userCode)
{
    IQueryable<User> users = GetUserSet();
    var results = from u in users
                  where u.UserCode.Equals(userCode)
                  select u;
    return results.FirstOrDefault() != null;
}
Run Code Online (Sandbox Code Playgroud)

您只需要设置GetUserSet返回列表即可.此类测试存在一些主要问题:

  • 你没有测试真正的实现 - 在EF模拟集的情况下是愚蠢的方法,因为一旦你这样做,你将linq-to-entities更改为linq-to-objects.这两个是完全不同的,linq-to-entities只是linq-to-objects的小子集=你的单元测试可以通过linq-to-objects传递但你的代码在运行时会失败.
  • 使用此方法后,您无法使用,Include因为include依赖于DbQuery/ DbSet.您需要进行集成测试才能使用它.
  • 这不会测试您的延迟加载是否有效

更好的方法是从Validate方法中删除linq查询- 只需将它们称为对象的另一个虚方法.Validate使用模拟查询方法对您的方法进行单元测试,并使用集成测试来自行测试查询.