如何在Moqentity framework中调用SqlQuery

我已经能够使用此链接使用Moq从entity framework中模拟DbSet

但是,我现在想知道如何模拟对SqlQuery的调用。 不确定这是否可行或者如何依赖于模拟的db上下文知道正在调用什么“查询”。

以下是我试图嘲笑的内容。

 var myObjects = DbContext.Database .SqlQuery("exec [dbo].[my_sproc] {0}", "some_value") .ToList(); 

我目前还没有尝试任何东西,因为不知道如何开始嘲笑这个例子。

DbSet是在下面并重新迭代,我可以正确地模拟返回MyObjectDbSet ,但现在我正在尝试模拟一个返回MyObject列表的SqlQuery。

 var dbContext = new Mock(); dbContext.Setup(m => m.MyObjects).Returns(mockObjects.Object); dbContext.Setup(m => m.Database.SqlQuery... something along these lines 

Database.SqlQuery未标记为虚拟,但Set.SqlQuery标记为虚拟。

基于Database.SqlQuery文档

即使返回的对象类型是实体类型,上下文也不会跟踪此查询的结果。 使用’SqlQuery(String,Object [])’方法返回上下文跟踪的实体。

Set.SqlQuery文档

默认情况下,返回的实体由上下文跟踪; 这可以通过在返回的DbRawSqlQuery上调用AsNoTracking来更改。

那么Database.SqlQuery(String, Object[])应该与Set.SqlQuery(String, Object[]).AsNoTracking() (仅当T是EF实体,而不是DTO / VM) 。

因此,如果您可以将实现替换为:

 var myObjects = DbContext .Set() .SqlQuery("exec [dbo].[my_sproc] {0}", "some_value") .AsNoTracking() .ToList(); 

你可以按照以下方式嘲笑它

 var list = new[] { new MyObject { Property = "some_value" }, new MyObject { Property = "some_value" }, new MyObject { Property = "another_value" } }; var setMock = new Mock>(); setMock.Setup(m => m.SqlQuery(It.IsAny(), It.IsAny())) .Returns((sql, param) => { // Filters by property. var filteredList = param.Length == 1 ? list.Where(x => x.Property == param[0] as string) : list; var sqlQueryMock = new Mock>(); sqlQueryMock.Setup(m => m.AsNoTracking()) .Returns(sqlQueryMock.Object); sqlQueryMock.Setup(m => m.GetEnumerator()) .Returns(filteredList.GetEnumerator()); return sqlQueryMock.Object; }); var contextMock = new Mock(); contextMock.Setup(m => m.Set()).Returns(setMock.Object); 

Database属性和SqlQuery方法未标记为virtual因此无法virtual它们(使用Moq;您可以使用不同的库来解释此问题,但这可能比您更喜欢惯性)。

您需要使用某种抽象来解决此问题,例如将数据库的整个查询包装在辅助类中:

 public interface IQueryHelper { IList DoYourQuery(string value); } public class QueryHelper : IQueryHelper { readonly MyDbContext myDbContext; public QueryHelper(MyDbContext myDbContext) { this.myDbContext = myDbContext; } public IList DoYourQuery(string value) { return myDbContext.Database.SqlQuery("exec [dbo].[my_sproc] {0}", value).ToList(); } } 

现在您正在测试的方法变为:

 public void YourMethod() { var myObjects = queryHelper.DoYourQuery("some_value"); } 

然后你将IQueryHelper注入你正在测试的类的构造函数中并模拟它。

你将失去对DoYourQuery测试覆盖率,但现在查询非常简单,显然没有任何缺陷 。

您可以在数据库上下文中添加一个虚拟方法,您可以在unit testing中覆盖它:

 public partial class MyDatabaseContext : DbContext { ///  /// Allows you to override queries that use the Database property ///  public virtual List SqlQueryVirtual(string query) { return this.Database.SqlQuery(query).ToList(); } } 

任何人都应该遇到这个。 我用几种方法解决了这个问题。 解决这个问题的另一种方法。

  1. 我的上下文是通过接口抽象的。 我只需要一些方法:

     public interface IDatabaseContext { DbSet Set() where T : class; DbEntityEntry Entry(T entity) where T : class; int SaveChanges(); Task SaveChangesAsync(); void AddOrUpdateEntity(params TEntity[] entities) where TEntity : class; 

    }

  2. 我的所有数据库访问都是通过异步方法。 在尝试模拟它时会出现一系列新问题。 幸运的是 – 这里已经回答了。 您获得的exception与IDbAsyncEnumerable的缺少模拟有关。 使用提供的解决方案 – 我只是扩展了一点,以便我有一个帮助器来返回一个模拟所有预期属性的Mock>对象。

     public static Mock> CreateDbSqlQuery(IList data) where TEntity : class, new() { var source = data.AsQueryable(); var mock = new Mock>() {CallBase = true}; mock.As>().Setup(m => m.Expression).Returns(source.Expression); mock.As>().Setup(m => m.ElementType).Returns(source.ElementType); mock.As>().Setup(m => m.GetEnumerator()).Returns(source.GetEnumerator()); mock.As>().Setup(m => m.Provider).Returns(new TestDbAsyncQueryProvider(source.Provider)); mock.As>().Setup(m => m.GetAsyncEnumerator()).Returns(new TestDbAsyncEnumerator(data.GetEnumerator())); mock.As>().Setup(m => m.Create()).Returns(new TEntity()); mock.As>().Setup(m => m.Add(It.IsAny())).Returns(i => { data.Add(i); return i; }); mock.As>().Setup(m => m.Remove(It.IsAny())).Returns(i => { data.Remove(i); return i; }); return mock; } 
  3. 最后 – 使用@Yulium Chandra提供的解决方案 – 我使用模拟上下文测试原始SQL如下所示:

      public Mock> MockDbSet { get; } .... MockDbSet.Setup(x => x.SqlQuery(It.IsAny)) .Returns ((sql, param) => { var sqlQueryMock = MockHelper.CreateDbSqlQuery(Models); sqlQueryMock.Setup(x => x.AsNoTracking()) .Returns(sqlQueryMock.Object); return sqlQueryMock.Object; });